Merge "Update WM Extensions aar for animation background color"
diff --git a/ProtoLibraries.bp b/ProtoLibraries.bp
index 56d91b2..d8f693c 100644
--- a/ProtoLibraries.bp
+++ b/ProtoLibraries.bp
@@ -97,6 +97,8 @@
         ],
         type: "full",
     },
+    // b/267831518: Pin tradefed and dependencies to Java 11.
+    java_version: "11",
     // Protos have lots of MissingOverride and similar.
     errorprone: {
         enabled: false,
diff --git a/apex/jobscheduler/framework/java/android/app/AlarmManager.java b/apex/jobscheduler/framework/java/android/app/AlarmManager.java
index c2a72b7..3103fcf 100644
--- a/apex/jobscheduler/framework/java/android/app/AlarmManager.java
+++ b/apex/jobscheduler/framework/java/android/app/AlarmManager.java
@@ -36,7 +36,9 @@
 import android.os.HandlerExecutor;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.Process;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.os.WorkSource;
 import android.text.TextUtils;
 import android.util.Log;
@@ -90,6 +92,14 @@
 public class AlarmManager {
     private static final String TAG = "AlarmManager";
 
+    /**
+     * Prefix used by {{@link #makeTag(long, WorkSource)}} to make a tag on behalf of the caller
+     * when the {@link #set(int, long, long, long, OnAlarmListener, Handler, WorkSource)} API is
+     * used. This prefix is a unique sequence of characters to differentiate with other tags that
+     * apps may provide to other APIs that accept a listener callback.
+     */
+    private static final String GENERATED_TAG_PREFIX = "$android.alarm.generated";
+
     /** @hide */
     @IntDef(prefix = { "RTC", "ELAPSED" }, value = {
             RTC_WAKEUP,
@@ -912,6 +922,24 @@
     }
 
     /**
+     * This is only used to make an identifying tag for the deprecated
+     * {@link #set(int, long, long, long, OnAlarmListener, Handler, WorkSource)} API which doesn't
+     * accept a tag. For all other APIs, the tag provided by the app is used, even if it is
+     * {@code null}.
+     */
+    private static String makeTag(long triggerMillis, WorkSource ws) {
+        final StringBuilder tagBuilder = new StringBuilder(GENERATED_TAG_PREFIX);
+
+        tagBuilder.append(":");
+        final int attributionUid =
+                (ws == null || ws.isEmpty()) ? Process.myUid() : ws.getAttributionUid();
+        tagBuilder.append(UserHandle.formatUid(attributionUid));
+        tagBuilder.append(":");
+        tagBuilder.append(triggerMillis);
+        return tagBuilder.toString();
+    }
+
+    /**
      * Direct callback version of {@link #set(int, long, long, long, PendingIntent, WorkSource)}.
      * Note that repeating alarms must use the PendingIntent variant, not an OnAlarmListener.
      * <p>
@@ -937,8 +965,8 @@
     public void set(@AlarmType int type, long triggerAtMillis, long windowMillis,
             long intervalMillis, @NonNull OnAlarmListener listener, @Nullable Handler targetHandler,
             @Nullable WorkSource workSource) {
-        setImpl(type, triggerAtMillis, windowMillis, intervalMillis, 0, null, listener, null,
-                targetHandler, workSource, null);
+        setImpl(type, triggerAtMillis, windowMillis, intervalMillis, 0, null, listener,
+                makeTag(triggerAtMillis, workSource), targetHandler, workSource, null);
     }
 
     /**
diff --git a/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java b/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java
index 50a7a19..776d913 100644
--- a/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java
+++ b/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java
@@ -169,18 +169,18 @@
     }
 
     @Override
-    public boolean canRunLongJobs() {
+    public boolean canRunUserInitiatedJobs() {
         try {
-            return mBinder.canRunLongJobs(mContext.getOpPackageName());
+            return mBinder.canRunUserInitiatedJobs(mContext.getOpPackageName());
         } catch (RemoteException e) {
             return false;
         }
     }
 
     @Override
-    public boolean hasRunLongJobsPermission(String packageName, int userId) {
+    public boolean hasRunUserInitiatedJobsPermission(String packageName, int userId) {
         try {
-            return mBinder.hasRunLongJobsPermission(packageName, userId);
+            return mBinder.hasRunUserInitiatedJobsPermission(packageName, userId);
         } catch (RemoteException e) {
             return false;
         }
diff --git a/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl b/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl
index 1ea0c5b..416a2d8 100644
--- a/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl
+++ b/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl
@@ -39,8 +39,8 @@
     ParceledListSlice<JobInfo> getAllPendingJobsInNamespace(String namespace);
     JobInfo getPendingJob(String namespace, int jobId);
     int getPendingJobReason(String namespace, int jobId);
-    boolean canRunLongJobs(String packageName);
-    boolean hasRunLongJobsPermission(String packageName, int userId);
+    boolean canRunUserInitiatedJobs(String packageName);
+    boolean hasRunUserInitiatedJobsPermission(String packageName, int userId);
     List<JobInfo> getStartedJobs();
     ParceledListSlice getAllJobSnapshots();
     @EnforcePermission(allOf={"MANAGE_ACTIVITY_TASKS", "INTERACT_ACROSS_USERS_FULL"})
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
index d4110a3..11c13c4 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
@@ -1904,8 +1904,8 @@
          * and allow for rescheduling.
          *
          * <p>
-         * If the app doesn't hold the {@link android.Manifest.permission#RUN_LONG_JOBS} permission
-         * when scheduling a user-initiated job, JobScheduler will throw a
+         * If the app doesn't hold the {@link android.Manifest.permission#RUN_USER_INITIATED_JOBS}
+         * permission when scheduling a user-initiated job, JobScheduler will throw a
          * {@link SecurityException}.
          *
          * <p>
@@ -1914,7 +1914,7 @@
          *
          * @see JobInfo#isUserInitiated()
          */
-        @RequiresPermission(android.Manifest.permission.RUN_LONG_JOBS)
+        @RequiresPermission(android.Manifest.permission.RUN_USER_INITIATED_JOBS)
         @NonNull
         public Builder setUserInitiated(boolean userInitiated) {
             if (userInitiated) {
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
index 4be8766..242b52c 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
@@ -424,7 +424,7 @@
      * {@code true}. This will return {@code false} if the job wasn't requested to run as a
      * user-initiated job, or if it was requested to run as a user-initiated job but the app didn't
      * meet any of the requirements at the time of execution, such as having the
-     * {@link android.Manifest.permission#RUN_LONG_JOBS} permission.
+     * {@link android.Manifest.permission#RUN_USER_INITIATED_JOBS} permission.
      *
      * @see JobInfo.Builder#setUserInitiated(boolean)
      */
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
index 3ba5c31..b8847ad 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
@@ -454,20 +454,23 @@
 
     /**
      * Returns {@code true} if the calling app currently holds the
-     * {@link android.Manifest.permission#RUN_LONG_JOBS} permission, allowing it to run long jobs.
+     * {@link android.Manifest.permission#RUN_USER_INITIATED_JOBS} permission, allowing it to run
+     * user-initiated jobs.
      */
-    public boolean canRunLongJobs() {
+    public boolean canRunUserInitiatedJobs() {
         return false;
     }
 
     /**
      * Returns {@code true} if the app currently holds the
-     * {@link android.Manifest.permission#RUN_LONG_JOBS} permission, allowing it to run long jobs.
+     * {@link android.Manifest.permission#RUN_USER_INITIATED_JOBS} permission, allowing it to run
+     * user-initiated jobs.
      * @hide
      * TODO(255371817): consider exposing this to apps who could call
      * {@link #scheduleAsPackage(JobInfo, String, int, String)}
      */
-    public boolean hasRunLongJobsPermission(@NonNull String packageName, @UserIdInt int userId) {
+    public boolean hasRunUserInitiatedJobsPermission(@NonNull String packageName,
+            @UserIdInt int userId) {
         return false;
     }
 
diff --git a/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java b/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java
index 217b8b6..c956bf5 100644
--- a/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java
+++ b/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java
@@ -17,9 +17,12 @@
 package com.android.server.job;
 
 import android.annotation.Nullable;
+import android.app.job.JobInfo;
 import android.app.job.JobParameters;
 import android.util.proto.ProtoOutputStream;
 
+import java.util.List;
+
 /**
  * JobScheduler local system service interface.
  * {@hide} Only for use within the system server.
@@ -27,6 +30,11 @@
 public interface JobSchedulerInternal {
 
     /**
+     * Returns a list of jobs scheduled by the system service for itself.
+     */
+    List<JobInfo> getSystemScheduledOwnJobs(@Nullable String namespace);
+
+    /**
      * Cancel the jobs for a given uid (e.g. when app data is cleared)
      *
      * @param includeProxiedJobs Include jobs scheduled for this UID by other apps
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 fce75a2..419111a 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -953,8 +953,8 @@
                     properties.getLong(
                             KEY_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS,
                             DEFAULT_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS));
-            // Data transfer requires RUN_LONG_JOBS permission, so the upper limit will be higher
-            // than other jobs.
+            // User-initiated requires RUN_USER_INITIATED_JOBS permission, so the upper limit will
+            // be higher than other jobs.
             // Max limit should be the min guarantee and the max of other user-initiated jobs.
             RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS = Math.max(
                     RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS,
@@ -3305,7 +3305,8 @@
     public long getMinJobExecutionGuaranteeMs(JobStatus job) {
         synchronized (mLock) {
             if (job.shouldTreatAsUserInitiatedJob()
-                    && checkRunLongJobsPermission(job.getSourceUid(), job.getSourcePackageName())) {
+                    && checkRunUserInitiatedJobsPermission(
+                            job.getSourceUid(), job.getSourcePackageName())) {
                 if (job.getJob().isDataTransfer()) {
                     final long estimatedTransferTimeMs =
                             mConnectivityController.getEstimatedTransferTimeMs(job);
@@ -3343,7 +3344,8 @@
     public long getMaxJobExecutionTimeMs(JobStatus job) {
         synchronized (mLock) {
             final boolean allowLongerJob = job.shouldTreatAsUserInitiatedJob()
-                    && checkRunLongJobsPermission(job.getSourceUid(), job.getSourcePackageName());
+                    && checkRunUserInitiatedJobsPermission(
+                            job.getSourceUid(), job.getSourcePackageName());
             if (job.getJob().isDataTransfer() && allowLongerJob) { // UI+DT
                 return mConstants.RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS;
             }
@@ -3536,6 +3538,21 @@
     final class LocalService implements JobSchedulerInternal {
 
         @Override
+        public List<JobInfo> getSystemScheduledOwnJobs(@Nullable String namespace) {
+            synchronized (mLock) {
+                final List<JobInfo> ownJobs = new ArrayList<>();
+                mJobs.forEachJob(Process.SYSTEM_UID, (job) -> {
+                    if (job.getSourceUid() == Process.SYSTEM_UID
+                            && Objects.equals(job.getNamespace(), namespace)
+                            && "android".equals(job.getSourcePackageName())) {
+                        ownJobs.add(job.getJob());
+                    }
+                });
+                return ownJobs;
+            }
+        }
+
+        @Override
         public void cancelJobsForUid(int uid, boolean includeProxiedJobs,
                 @JobParameters.StopReason int reason, int internalReasonCode, String debugReason) {
             JobSchedulerService.this.cancelJobsForUid(uid,
@@ -3809,7 +3826,7 @@
                 if (sourceUid != -1) {
                     // Check the permission of the source app.
                     final int sourceResult =
-                            validateRunLongJobsPermission(sourceUid, sourcePkgName);
+                            validateRunUserInitiatedJobsPermission(sourceUid, sourcePkgName);
                     if (sourceResult != JobScheduler.RESULT_SUCCESS) {
                         return sourceResult;
                     }
@@ -3819,7 +3836,7 @@
                     // Source app is different from calling app. Make sure the calling app also has
                     // the permission.
                     final int callingResult =
-                            validateRunLongJobsPermission(callingUid, callingPkgName);
+                            validateRunUserInitiatedJobsPermission(callingUid, callingPkgName);
                     if (callingResult != JobScheduler.RESULT_SUCCESS) {
                         return callingResult;
                     }
@@ -3856,10 +3873,10 @@
             return JobScheduler.RESULT_SUCCESS;
         }
 
-        private int validateRunLongJobsPermission(int uid, String packageName) {
-            final int state = getRunLongJobsPermissionState(uid, packageName);
+        private int validateRunUserInitiatedJobsPermission(int uid, String packageName) {
+            final int state = getRunUserInitiatedJobsPermissionState(uid, packageName);
             if (state == PermissionChecker.PERMISSION_HARD_DENIED) {
-                throw new SecurityException(android.Manifest.permission.RUN_LONG_JOBS
+                throw new SecurityException(android.Manifest.permission.RUN_USER_INITIATED_JOBS
                         + " required to schedule user-initiated jobs.");
             }
             if (state == PermissionChecker.PERMISSION_SOFT_DENIED) {
@@ -4076,30 +4093,29 @@
             }
         }
 
-        @Override
-        public boolean canRunLongJobs(@NonNull String packageName) {
+        public boolean canRunUserInitiatedJobs(@NonNull String packageName) {
             final int callingUid = Binder.getCallingUid();
             final int userId = UserHandle.getUserId(callingUid);
             final int packageUid = mLocalPM.getPackageUid(packageName, 0, userId);
             if (callingUid != packageUid) {
                 throw new SecurityException("Uid " + callingUid
-                        + " cannot query canRunLongJobs for package " + packageName);
+                        + " cannot query canRunUserInitiatedJobs for package " + packageName);
             }
 
-            return checkRunLongJobsPermission(packageUid, packageName);
+            return checkRunUserInitiatedJobsPermission(packageUid, packageName);
         }
 
-        @Override
-        public boolean hasRunLongJobsPermission(@NonNull String packageName,
+        public boolean hasRunUserInitiatedJobsPermission(@NonNull String packageName,
                 @UserIdInt int userId) {
             final int uid = mLocalPM.getPackageUid(packageName, 0, userId);
             final int callingUid = Binder.getCallingUid();
             if (callingUid != uid && !UserHandle.isCore(callingUid)) {
                 throw new SecurityException("Uid " + callingUid
-                        + " cannot query hasRunLongJobsPermission for package " + packageName);
+                        + " cannot query hasRunUserInitiatedJobsPermission for package "
+                        + packageName);
             }
 
-            return checkRunLongJobsPermission(uid, packageName);
+            return checkRunUserInitiatedJobsPermission(uid, packageName);
         }
 
         /**
@@ -4479,14 +4495,14 @@
     }
 
     /** Returns true if both the appop and permission are granted. */
-    private boolean checkRunLongJobsPermission(int packageUid, String packageName) {
-        return getRunLongJobsPermissionState(packageUid, packageName)
+    private boolean checkRunUserInitiatedJobsPermission(int packageUid, String packageName) {
+        return getRunUserInitiatedJobsPermissionState(packageUid, packageName)
                 == PermissionChecker.PERMISSION_GRANTED;
     }
 
-    private int getRunLongJobsPermissionState(int packageUid, String packageName) {
+    private int getRunUserInitiatedJobsPermissionState(int packageUid, String packageName) {
         return PermissionChecker.checkPermissionForPreflight(getTestableContext(),
-                android.Manifest.permission.RUN_LONG_JOBS, PermissionChecker.PID_UNKNOWN,
+                android.Manifest.permission.RUN_USER_INITIATED_JOBS, PermissionChecker.PID_UNKNOWN,
                 packageUid, packageName);
     }
 
diff --git a/boot/preloaded-classes b/boot/preloaded-classes
index 39b7385..b43421d 100644
--- a/boot/preloaded-classes
+++ b/boot/preloaded-classes
@@ -5525,6 +5525,8 @@
 android.nfc.NfcAdapter
 android.nfc.NfcControllerAlwaysOnListener
 android.nfc.NfcManager
+android.nfc.NfcServiceManager$ServiceRegisterer
+android.nfc.NfcServiceManager
 android.nfc.Tag$1
 android.nfc.Tag
 android.nfc.TechListParcel$1
diff --git a/core/api/current.txt b/core/api/current.txt
index 896d13a..ad0278e 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -83,6 +83,7 @@
     field public static final String CLEAR_APP_CACHE = "android.permission.CLEAR_APP_CACHE";
     field public static final String CONFIGURE_WIFI_DISPLAY = "android.permission.CONFIGURE_WIFI_DISPLAY";
     field public static final String CONTROL_LOCATION_UPDATES = "android.permission.CONTROL_LOCATION_UPDATES";
+    field public static final String CREDENTIAL_MANAGER_SET_ORIGIN = "android.permission.CREDENTIAL_MANAGER_SET_ORIGIN";
     field public static final String DELETE_CACHE_FILES = "android.permission.DELETE_CACHE_FILES";
     field public static final String DELETE_PACKAGES = "android.permission.DELETE_PACKAGES";
     field public static final String DELIVER_COMPANION_MESSAGES = "android.permission.DELIVER_COMPANION_MESSAGES";
@@ -209,7 +210,7 @@
     field public static final String REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE = "android.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE";
     field public static final String REQUEST_PASSWORD_COMPLEXITY = "android.permission.REQUEST_PASSWORD_COMPLEXITY";
     field @Deprecated public static final String RESTART_PACKAGES = "android.permission.RESTART_PACKAGES";
-    field public static final String RUN_LONG_JOBS = "android.permission.RUN_LONG_JOBS";
+    field public static final String RUN_USER_INITIATED_JOBS = "android.permission.RUN_USER_INITIATED_JOBS";
     field public static final String SCHEDULE_EXACT_ALARM = "android.permission.SCHEDULE_EXACT_ALARM";
     field public static final String SEND_RESPOND_VIA_MESSAGE = "android.permission.SEND_RESPOND_VIA_MESSAGE";
     field public static final String SEND_SMS = "android.permission.SEND_SMS";
@@ -7708,7 +7709,7 @@
     method @RequiresPermission(value=android.Manifest.permission.READ_NEARBY_STREAMING_POLICY, conditional=true) public int getNearbyAppStreamingPolicy();
     method @RequiresPermission(value=android.Manifest.permission.READ_NEARBY_STREAMING_POLICY, conditional=true) public int getNearbyNotificationStreamingPolicy();
     method @Deprecated @ColorInt public int getOrganizationColor(@NonNull android.content.ComponentName);
-    method @Nullable public CharSequence getOrganizationName(@NonNull android.content.ComponentName);
+    method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY) public CharSequence getOrganizationName(@Nullable android.content.ComponentName);
     method public java.util.List<android.telephony.data.ApnSetting> getOverrideApns(@NonNull android.content.ComponentName);
     method @NonNull public android.app.admin.DevicePolicyManager getParentProfileInstance(@NonNull android.content.ComponentName);
     method @RequiresPermission(android.Manifest.permission.REQUEST_PASSWORD_COMPLEXITY) public int getPasswordComplexity();
@@ -7863,7 +7864,7 @@
     method public void setNetworkLoggingEnabled(@Nullable android.content.ComponentName, boolean);
     method @Deprecated public void setOrganizationColor(@NonNull android.content.ComponentName, int);
     method public void setOrganizationId(@NonNull String);
-    method public void setOrganizationName(@NonNull android.content.ComponentName, @Nullable CharSequence);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY) public void setOrganizationName(@Nullable android.content.ComponentName, @Nullable CharSequence);
     method public void setOverrideApnsEnabled(@NonNull android.content.ComponentName, boolean);
     method @NonNull public String[] setPackagesSuspended(@NonNull android.content.ComponentName, @NonNull String[], boolean);
     method public void setPasswordExpirationTimeout(@NonNull android.content.ComponentName, long);
@@ -8190,6 +8191,7 @@
     method public int getResultCode();
     field public static final int RESULT_FAILURE_CONFLICTING_ADMIN_POLICY = 1; // 0x1
     field public static final int RESULT_FAILURE_UNKNOWN = -1; // 0xffffffff
+    field public static final int RESULT_POLICY_CLEARED = 2; // 0x2
     field public static final int RESULT_SUCCESS = 0; // 0x0
   }
 
@@ -8694,7 +8696,7 @@
     method public android.app.job.JobInfo.Builder setTransientExtras(@NonNull android.os.Bundle);
     method public android.app.job.JobInfo.Builder setTriggerContentMaxDelay(long);
     method public android.app.job.JobInfo.Builder setTriggerContentUpdateDelay(long);
-    method @NonNull @RequiresPermission(android.Manifest.permission.RUN_LONG_JOBS) public android.app.job.JobInfo.Builder setUserInitiated(boolean);
+    method @NonNull @RequiresPermission(android.Manifest.permission.RUN_USER_INITIATED_JOBS) public android.app.job.JobInfo.Builder setUserInitiated(boolean);
   }
 
   public static final class JobInfo.TriggerContentUri implements android.os.Parcelable {
@@ -8746,7 +8748,7 @@
 
   public abstract class JobScheduler {
     ctor public JobScheduler();
-    method public boolean canRunLongJobs();
+    method public boolean canRunUserInitiatedJobs();
     method public abstract void cancel(int);
     method public abstract void cancelAll();
     method public void cancelInAllNamespaces();
@@ -13465,7 +13467,7 @@
     ctor public CreateCredentialException(@NonNull String);
     method @NonNull public String getType();
     field @NonNull public static final String TYPE_INTERRUPTED = "android.credentials.CreateCredentialException.TYPE_INTERRUPTED";
-    field @NonNull public static final String TYPE_NO_CREDENTIAL = "android.credentials.CreateCredentialException.TYPE_NO_CREDENTIAL";
+    field @NonNull public static final String TYPE_NO_CREATE_OPTIONS = "android.credentials.CreateCredentialException.TYPE_NO_CREATE_OPTIONS";
     field @NonNull public static final String TYPE_UNKNOWN = "android.credentials.CreateCredentialException.TYPE_UNKNOWN";
     field @NonNull public static final String TYPE_USER_CANCELED = "android.credentials.CreateCredentialException.TYPE_USER_CANCELED";
   }
@@ -13514,7 +13516,9 @@
   public final class CredentialManager {
     method public void clearCredentialState(@NonNull android.credentials.ClearCredentialStateRequest, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.credentials.ClearCredentialStateException>);
     method public void createCredential(@NonNull android.credentials.CreateCredentialRequest, @NonNull android.app.Activity, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.credentials.CreateCredentialResponse,android.credentials.CreateCredentialException>);
+    method @RequiresPermission(android.Manifest.permission.CREDENTIAL_MANAGER_SET_ORIGIN) public void createCredentialWithOrigin(@NonNull android.credentials.CreateCredentialRequest, @Nullable String, @NonNull android.app.Activity, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.credentials.CreateCredentialResponse,android.credentials.CreateCredentialException>);
     method public void getCredential(@NonNull android.credentials.GetCredentialRequest, @NonNull android.app.Activity, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.GetCredentialException>);
+    method @RequiresPermission(android.Manifest.permission.CREDENTIAL_MANAGER_SET_ORIGIN) public void getCredentialWithOrigin(@NonNull android.credentials.GetCredentialRequest, @Nullable String, @NonNull android.app.Activity, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.GetCredentialException>);
     method public boolean isEnabledCredentialProviderService(@NonNull android.content.ComponentName);
     method public void registerCredentialDescription(@NonNull android.credentials.RegisterCredentialDescriptionRequest);
     method public void unregisterCredentialDescription(@NonNull android.credentials.UnregisterCredentialDescriptionRequest);
@@ -15644,26 +15648,31 @@
   }
 
   public class MeshSpecification {
-    method @NonNull public static android.graphics.MeshSpecification make(@NonNull java.util.List<android.graphics.MeshSpecification.Attribute>, int, @NonNull java.util.List<android.graphics.MeshSpecification.Varying>, @NonNull String, @NonNull String);
-    method @NonNull public static android.graphics.MeshSpecification make(@NonNull java.util.List<android.graphics.MeshSpecification.Attribute>, int, @NonNull java.util.List<android.graphics.MeshSpecification.Varying>, @NonNull String, @NonNull String, @NonNull android.graphics.ColorSpace);
-    method @NonNull public static android.graphics.MeshSpecification make(@NonNull java.util.List<android.graphics.MeshSpecification.Attribute>, int, @NonNull java.util.List<android.graphics.MeshSpecification.Varying>, @NonNull String, @NonNull String, @NonNull android.graphics.ColorSpace, int);
-    field public static final int FLOAT = 0; // 0x0
-    field public static final int FLOAT2 = 1; // 0x1
-    field public static final int FLOAT3 = 2; // 0x2
-    field public static final int FLOAT4 = 3; // 0x3
-    field public static final int OPAQUE = 1; // 0x1
-    field public static final int PREMUL = 2; // 0x2
-    field public static final int UBYTE4 = 4; // 0x4
-    field public static final int UNKNOWN = 0; // 0x0
-    field public static final int UNPREMULT = 3; // 0x3
+    method @NonNull public static android.graphics.MeshSpecification make(@NonNull @Size(max=8) android.graphics.MeshSpecification.Attribute[], @IntRange(from=1, to=1024) int, @NonNull @Size(max=6) android.graphics.MeshSpecification.Varying[], @NonNull String, @NonNull String);
+    method @NonNull public static android.graphics.MeshSpecification make(@NonNull @Size(max=8) android.graphics.MeshSpecification.Attribute[], @IntRange(from=1, to=1024) int, @NonNull @Size(max=6) android.graphics.MeshSpecification.Varying[], @NonNull String, @NonNull String, @NonNull android.graphics.ColorSpace);
+    method @NonNull public static android.graphics.MeshSpecification make(@NonNull @Size(max=8) android.graphics.MeshSpecification.Attribute[], @IntRange(from=1, to=1024) int, @NonNull @Size(max=6) android.graphics.MeshSpecification.Varying[], @NonNull String, @NonNull String, @NonNull android.graphics.ColorSpace, int);
+    field public static final int ALPHA_TYPE_OPAQUE = 1; // 0x1
+    field public static final int ALPHA_TYPE_PREMUL = 2; // 0x2
+    field public static final int ALPHA_TYPE_PREMULT = 3; // 0x3
+    field public static final int ALPHA_TYPE_UNKNOWN = 0; // 0x0
+    field public static final int TYPE_FLOAT = 0; // 0x0
+    field public static final int TYPE_FLOAT2 = 1; // 0x1
+    field public static final int TYPE_FLOAT3 = 2; // 0x2
+    field public static final int TYPE_FLOAT4 = 3; // 0x3
+    field public static final int TYPE_UBYTE4 = 4; // 0x4
   }
 
   public static class MeshSpecification.Attribute {
     ctor public MeshSpecification.Attribute(int, int, @NonNull String);
+    method @NonNull public String getName();
+    method public int getOffset();
+    method public int getType();
   }
 
   public static class MeshSpecification.Varying {
     ctor public MeshSpecification.Varying(int, @NonNull String);
+    method @NonNull public String getName();
+    method public int getType();
   }
 
   @Deprecated public class Movie {
@@ -26536,13 +26545,25 @@
     method public int getTableName();
     method public int getVersion();
     field @NonNull public static final android.os.Parcelable.Creator<android.media.tv.TableRequest> CREATOR;
+    field public static final int TABLE_NAME_BAT = 4; // 0x4
+    field public static final int TABLE_NAME_CAT = 2; // 0x2
+    field public static final int TABLE_NAME_EIT = 6; // 0x6
+    field public static final int TABLE_NAME_NIT = 3; // 0x3
     field public static final int TABLE_NAME_PAT = 0; // 0x0
     field public static final int TABLE_NAME_PMT = 1; // 0x1
+    field public static final int TABLE_NAME_SDT = 5; // 0x5
+    field public static final int TABLE_NAME_SIT = 9; // 0x9
+    field public static final int TABLE_NAME_TDT = 7; // 0x7
+    field public static final int TABLE_NAME_TOT = 8; // 0x8
   }
 
   public final class TableResponse extends android.media.tv.BroadcastInfoResponse implements android.os.Parcelable {
     ctor public TableResponse(int, int, int, @Nullable android.net.Uri, int, int);
+    ctor public TableResponse(int, int, int, @NonNull byte[], int, int);
+    ctor public TableResponse(int, int, int, @NonNull android.os.SharedMemory, int, int);
     method public int getSize();
+    method @Nullable public byte[] getTableByteArray();
+    method @Nullable public android.os.SharedMemory getTableSharedMemory();
     method @Nullable public android.net.Uri getTableUri();
     method public int getVersion();
     field @NonNull public static final android.os.Parcelable.Creator<android.media.tv.TableResponse> CREATOR;
@@ -26550,7 +26571,9 @@
 
   public final class TimelineRequest extends android.media.tv.BroadcastInfoRequest implements android.os.Parcelable {
     ctor public TimelineRequest(int, int, int);
+    ctor public TimelineRequest(int, int, int, @NonNull String);
     method public int getIntervalMillis();
+    method @Nullable public String getSelector();
     field @NonNull public static final android.os.Parcelable.Creator<android.media.tv.TimelineRequest> CREATOR;
   }
 
@@ -27450,9 +27473,12 @@
     field public static final String COMMAND_PARAMETER_KEY_CHANGE_CHANNEL_QUIETLY = "command_change_channel_quietly";
     field public static final String COMMAND_PARAMETER_KEY_CHANNEL_URI = "command_channel_uri";
     field public static final String COMMAND_PARAMETER_KEY_INPUT_ID = "command_input_id";
+    field public static final String COMMAND_PARAMETER_KEY_STOP_MODE = "command_stop_mode";
     field public static final String COMMAND_PARAMETER_KEY_TRACK_ID = "command_track_id";
     field public static final String COMMAND_PARAMETER_KEY_TRACK_TYPE = "command_track_type";
     field public static final String COMMAND_PARAMETER_KEY_VOLUME = "command_volume";
+    field public static final int COMMAND_PARAMETER_VALUE_STOP_MODE_BLANK = 1; // 0x1
+    field public static final int COMMAND_PARAMETER_VALUE_STOP_MODE_FREEZE = 2; // 0x2
     field public static final String PLAYBACK_COMMAND_TYPE_SELECT_TRACK = "select_track";
     field public static final String PLAYBACK_COMMAND_TYPE_SET_STREAM_VOLUME = "set_stream_volume";
     field public static final String PLAYBACK_COMMAND_TYPE_STOP = "stop";
@@ -27481,6 +27507,7 @@
     method public void onCurrentChannelLcn(int);
     method public void onCurrentChannelUri(@Nullable android.net.Uri);
     method public void onCurrentTvInputId(@Nullable String);
+    method public void onCurrentVideoBounds(@NonNull android.graphics.Rect);
     method public void onDestroyBiInteractiveAppRequest(@NonNull String);
     method public void onError(@NonNull String, @NonNull android.os.Bundle);
     method public boolean onGenericMotionEvent(@NonNull android.view.MotionEvent);
@@ -27518,6 +27545,7 @@
     method @CallSuper public void requestCurrentChannelLcn();
     method @CallSuper public void requestCurrentChannelUri();
     method @CallSuper public void requestCurrentTvInputId();
+    method @CallSuper public void requestCurrentVideoBounds();
     method @CallSuper public void requestSigning(@NonNull String, @NonNull String, @NonNull String, @NonNull byte[]);
     method @CallSuper public void requestStartRecording(@Nullable android.net.Uri);
     method @CallSuper public void requestStopRecording(@NonNull String);
@@ -27570,6 +27598,7 @@
     method public void sendCurrentChannelLcn(int);
     method public void sendCurrentChannelUri(@Nullable android.net.Uri);
     method public void sendCurrentTvInputId(@Nullable String);
+    method public void sendCurrentVideoBounds(@NonNull android.graphics.Rect);
     method public void sendSigningResult(@NonNull String, @NonNull byte[]);
     method public void sendStreamVolume(float);
     method public void sendTrackInfoList(@Nullable java.util.List<android.media.tv.TvTrackInfo>);
@@ -27600,6 +27629,7 @@
     method public void onRequestCurrentChannelLcn(@NonNull String);
     method public void onRequestCurrentChannelUri(@NonNull String);
     method public void onRequestCurrentTvInputId(@NonNull String);
+    method public void onRequestCurrentVideoBounds(@NonNull String);
     method public void onRequestSigning(@NonNull String, @NonNull String, @NonNull String, @NonNull String, @NonNull byte[]);
     method public void onRequestStartRecording(@NonNull String, @Nullable android.net.Uri);
     method public void onRequestStopRecording(@NonNull String, @NonNull String);
@@ -40245,6 +40275,7 @@
   }
 
   public final class BeginCreateCredentialResponse implements android.os.Parcelable {
+    ctor public BeginCreateCredentialResponse();
     method public int describeContents();
     method @NonNull public java.util.List<android.service.credentials.CreateEntry> getCreateEntries();
     method @Nullable public android.service.credentials.CreateEntry getRemoteCreateEntry();
@@ -40264,6 +40295,7 @@
     ctor public BeginGetCredentialOption(@NonNull String, @NonNull String, @NonNull android.os.Bundle);
     method public int describeContents();
     method @NonNull public android.os.Bundle getCandidateQueryData();
+    method @NonNull public String getId();
     method @NonNull public String getType();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.service.credentials.BeginGetCredentialOption> CREATOR;
@@ -40286,6 +40318,7 @@
   }
 
   public final class BeginGetCredentialResponse implements android.os.Parcelable {
+    ctor public BeginGetCredentialResponse();
     method public int describeContents();
     method @NonNull public java.util.List<android.service.credentials.Action> getActions();
     method @NonNull public java.util.List<android.service.credentials.Action> getAuthenticationActions();
@@ -40979,7 +41012,7 @@
     method public void onRequestCompleteVoice(android.service.voice.VoiceInteractionSession.CompleteVoiceRequest);
     method public void onRequestConfirmation(android.service.voice.VoiceInteractionSession.ConfirmationRequest);
     method public void onRequestPickOption(android.service.voice.VoiceInteractionSession.PickOptionRequest);
-    method public void onShow(android.os.Bundle, int);
+    method public void onShow(@Nullable android.os.Bundle, int);
     method public void onTaskFinished(android.content.Intent, int);
     method public void onTaskStarted(android.content.Intent, int);
     method public void onTrimMemory(int);
@@ -43377,7 +43410,7 @@
     field public static final String KEY_PREFIX = "imssms.";
     field public static final String KEY_SMS_CSFB_RETRY_ON_FAILURE_BOOL = "imssms.sms_csfb_retry_on_failure_bool";
     field public static final String KEY_SMS_MAX_RETRY_COUNT_INT = "imssms.sms_max_retry_count_int";
-    field public static final String KEY_SMS_MAX_RETRY_COUNT_OVER_IMS_INT = "imssms.sms_max_retry_count_over_ims_int";
+    field public static final String KEY_SMS_MAX_RETRY_OVER_IMS_COUNT_INT = "imssms.sms_max_retry_over_ims_count_int";
     field public static final String KEY_SMS_OVER_IMS_FORMAT_INT = "imssms.sms_over_ims_format_int";
     field public static final String KEY_SMS_OVER_IMS_SEND_RETRY_DELAY_MILLIS_INT = "imssms.sms_over_ims_send_retry_delay_millis_int";
     field public static final String KEY_SMS_OVER_IMS_SUPPORTED_BOOL = "imssms.sms_over_ims_supported_bool";
@@ -44011,6 +44044,7 @@
     field public static final int IWLAN_SEMANTIC_ERROR_IN_THE_TFT_OPERATION = 8241; // 0x2031
     field public static final int IWLAN_SYNTACTICAL_ERRORS_IN_PACKET_FILTERS = 8245; // 0x2035
     field public static final int IWLAN_SYNTACTICAL_ERROR_IN_THE_TFT_OPERATION = 8242; // 0x2032
+    field public static final int IWLAN_TUNNEL_NOT_FOUND = 16390; // 0x4006
     field public static final int IWLAN_UNAUTHENTICATED_EMERGENCY_NOT_SUPPORTED = 11055; // 0x2b2f
     field public static final int IWLAN_USER_UNKNOWN = 9001; // 0x2329
     field public static final int LIMITED_TO_IPV4 = 2234; // 0x8ba
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index af35d96..5171027 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -316,6 +316,30 @@
 
 }
 
+package android.nfc {
+
+  public class NfcFrameworkInitializer {
+    method public static void registerServiceWrappers();
+    method public static void setNfcServiceManager(@NonNull android.nfc.NfcServiceManager);
+  }
+
+  public class NfcServiceManager {
+    method @NonNull public android.nfc.NfcServiceManager.ServiceRegisterer getNfcManagerServiceRegisterer();
+  }
+
+  public static class NfcServiceManager.ServiceNotFoundException extends java.lang.Exception {
+    ctor public NfcServiceManager.ServiceNotFoundException(@NonNull String);
+  }
+
+  public static final class NfcServiceManager.ServiceRegisterer {
+    method @Nullable public android.os.IBinder get();
+    method @NonNull public android.os.IBinder getOrThrow() throws android.nfc.NfcServiceManager.ServiceNotFoundException;
+    method public void register(@NonNull android.os.IBinder);
+    method @Nullable public android.os.IBinder tryGet();
+  }
+
+}
+
 package android.os {
 
   public class ArtModuleServiceManager {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index fee4001..c948f05 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -100,6 +100,7 @@
     field public static final String CHANGE_APP_IDLE_STATE = "android.permission.CHANGE_APP_IDLE_STATE";
     field public static final String CHANGE_APP_LAUNCH_TIME_ESTIMATE = "android.permission.CHANGE_APP_LAUNCH_TIME_ESTIMATE";
     field public static final String CHANGE_DEVICE_IDLE_TEMP_WHITELIST = "android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST";
+    field public static final String CHECK_REMOTE_LOCKSCREEN = "android.permission.CHECK_REMOTE_LOCKSCREEN";
     field public static final String CLEAR_APP_USER_DATA = "android.permission.CLEAR_APP_USER_DATA";
     field public static final String COMPANION_APPROVE_WIFI_CONNECTIONS = "android.permission.COMPANION_APPROVE_WIFI_CONNECTIONS";
     field public static final String CONFIGURE_DISPLAY_BRIGHTNESS = "android.permission.CONFIGURE_DISPLAY_BRIGHTNESS";
@@ -246,6 +247,8 @@
     field public static final String PERFORM_IMS_SINGLE_REGISTRATION = "android.permission.PERFORM_IMS_SINGLE_REGISTRATION";
     field public static final String PERFORM_SIM_ACTIVATION = "android.permission.PERFORM_SIM_ACTIVATION";
     field public static final String POWER_SAVER = "android.permission.POWER_SAVER";
+    field public static final String PROVIDE_DEFAULT_ENABLED_CREDENTIAL_SERVICE = "android.permission.PROVIDE_DEFAULT_ENABLED_CREDENTIAL_SERVICE";
+    field public static final String PROVIDE_HYBRID_CREDENTIAL_SERVICE = "android.permission.PROVIDE_HYBRID_CREDENTIAL_SERVICE";
     field public static final String PROVIDE_RESOLVER_RANKER_SERVICE = "android.permission.PROVIDE_RESOLVER_RANKER_SERVICE";
     field public static final String PROVIDE_TRUST_AGENT = "android.permission.PROVIDE_TRUST_AGENT";
     field public static final String PROVISION_DEMO_DEVICE = "android.permission.PROVISION_DEMO_DEVICE";
@@ -919,7 +922,9 @@
     method @RequiresPermission(android.Manifest.permission.SHOW_KEYGUARD_MESSAGE) public void requestDismissKeyguard(@NonNull android.app.Activity, @Nullable CharSequence, @Nullable android.app.KeyguardManager.KeyguardDismissCallback);
     method @RequiresPermission("android.permission.SET_INITIAL_LOCK") public boolean setLock(int, @NonNull byte[], int);
     method @RequiresPermission(android.Manifest.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS) public void setPrivateNotificationsAllowed(boolean);
+    method @NonNull @RequiresPermission(android.Manifest.permission.CHECK_REMOTE_LOCKSCREEN) public android.app.StartLockscreenValidationRequest startRemoteLockscreenValidation();
     method @RequiresPermission(android.Manifest.permission.MANAGE_WEAK_ESCROW_TOKEN) public boolean unregisterWeakEscrowTokenRemovedListener(@NonNull android.app.KeyguardManager.WeakEscrowTokenRemovedListener);
+    method @NonNull @RequiresPermission(android.Manifest.permission.CHECK_REMOTE_LOCKSCREEN) public android.app.RemoteLockscreenValidationResult validateRemoteLockscreen(@NonNull byte[]);
     field public static final int PASSWORD = 0; // 0x0
     field public static final int PATTERN = 2; // 0x2
     field public static final int PIN = 1; // 0x1
@@ -996,6 +1001,25 @@
     field @RequiresPermission(android.Manifest.permission.STATUS_BAR_SERVICE) public static final String ACTION_TOGGLE_NOTIFICATION_HANDLER_PANEL = "android.app.action.TOGGLE_NOTIFICATION_HANDLER_PANEL";
   }
 
+  public final class RemoteLockscreenValidationResult implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getResultCode();
+    method public long getTimeoutMillis();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.RemoteLockscreenValidationResult> CREATOR;
+    field public static final int RESULT_GUESS_INVALID = 2; // 0x2
+    field public static final int RESULT_GUESS_VALID = 1; // 0x1
+    field public static final int RESULT_LOCKOUT = 3; // 0x3
+    field public static final int RESULT_NO_REMAINING_ATTEMPTS = 4; // 0x4
+  }
+
+  public static final class RemoteLockscreenValidationResult.Builder {
+    ctor public RemoteLockscreenValidationResult.Builder();
+    method @NonNull public android.app.RemoteLockscreenValidationResult build();
+    method @NonNull public android.app.RemoteLockscreenValidationResult.Builder setResultCode(int);
+    method @NonNull public android.app.RemoteLockscreenValidationResult.Builder setTimeoutMillis(long);
+  }
+
   public final class RuntimeAppOpAccessMessage implements android.os.Parcelable {
     ctor public RuntimeAppOpAccessMessage(@IntRange(from=0L) int, @IntRange(from=0L) int, @NonNull String, @Nullable String, @NonNull String, int);
     method public int describeContents();
@@ -1013,6 +1037,23 @@
     method public void launchAssist(@Nullable android.os.Bundle);
   }
 
+  public final class StartLockscreenValidationRequest implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getLockscreenUiType();
+    method public int getRemainingAttempts();
+    method @NonNull public byte[] getSourcePublicKey();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.StartLockscreenValidationRequest> CREATOR;
+  }
+
+  public static final class StartLockscreenValidationRequest.Builder {
+    ctor public StartLockscreenValidationRequest.Builder();
+    method @NonNull public android.app.StartLockscreenValidationRequest build();
+    method @NonNull public android.app.StartLockscreenValidationRequest.Builder setLockscreenUiType(int);
+    method @NonNull public android.app.StartLockscreenValidationRequest.Builder setRemainingAttempts(int);
+    method @NonNull public android.app.StartLockscreenValidationRequest.Builder setSourcePublicKey(@NonNull byte[]);
+  }
+
   public class StatusBarManager {
     method @NonNull @RequiresPermission(android.Manifest.permission.STATUS_BAR) public android.app.StatusBarManager.DisableInfo getDisableInfo();
     method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public int getNavBarMode();
@@ -1158,6 +1199,15 @@
 
 package android.app.admin {
 
+  public abstract class Authority implements android.os.Parcelable {
+    method public int describeContents();
+  }
+
+  public final class DeviceAdminAuthority extends android.app.admin.Authority {
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.DeviceAdminAuthority> CREATOR;
+  }
+
   public final class DevicePolicyDrawableResource implements android.os.Parcelable {
     ctor public DevicePolicyDrawableResource(@NonNull android.content.Context, @NonNull String, @NonNull String, @NonNull String, @DrawableRes int);
     ctor public DevicePolicyDrawableResource(@NonNull android.content.Context, @NonNull String, @NonNull String, @DrawableRes int);
@@ -1189,6 +1239,7 @@
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}) public String getDeviceOwnerNameOnAnyUser();
     method @Nullable public CharSequence getDeviceOwnerOrganizationName();
     method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public android.os.UserHandle getDeviceOwnerUser();
+    method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public android.app.admin.DevicePolicyState getDevicePolicyState();
     method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_ADMIN_POLICY}) public java.util.List<java.lang.String> getPermittedAccessibilityServices(int);
     method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_ADMIN_POLICY}) public java.util.List<java.lang.String> getPermittedInputMethodsForCurrentUser();
     method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public java.util.List<android.os.UserHandle> getPolicyManagedProfiles(@NonNull android.os.UserHandle);
@@ -1230,6 +1281,7 @@
     field public static final String ACTION_SET_PROFILE_OWNER = "android.app.action.SET_PROFILE_OWNER";
     field @Deprecated public static final String ACTION_STATE_USER_SETUP_COMPLETE = "android.app.action.STATE_USER_SETUP_COMPLETE";
     field @RequiresPermission(android.Manifest.permission.LAUNCH_DEVICE_MANAGER_SETUP) public static final String ACTION_UPDATE_DEVICE_POLICY_MANAGEMENT_ROLE_HOLDER = "android.app.action.UPDATE_DEVICE_POLICY_MANAGEMENT_ROLE_HOLDER";
+    field public static final int EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION = 2; // 0x2
     field public static final int EXEMPT_FROM_APP_STANDBY = 0; // 0x0
     field public static final int EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS = 1; // 0x1
     field public static final String EXTRA_FORCE_UPDATE_ROLE_HOLDER = "android.app.extra.FORCE_UPDATE_ROLE_HOLDER";
@@ -1316,6 +1368,14 @@
     method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES) public void setStrings(@NonNull java.util.Set<android.app.admin.DevicePolicyStringResource>);
   }
 
+  public final class DevicePolicyState implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public java.util.Map<android.os.UserHandle,java.util.Map<android.app.admin.PolicyKey,android.app.admin.PolicyState<?>>> getPoliciesForAllUsers();
+    method @NonNull public java.util.Map<android.app.admin.PolicyKey,android.app.admin.PolicyState<?>> getPoliciesForUser(@NonNull android.os.UserHandle);
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.DevicePolicyState> CREATOR;
+  }
+
   public final class DevicePolicyStringResource implements android.os.Parcelable {
     ctor public DevicePolicyStringResource(@NonNull android.content.Context, @NonNull String, @StringRes int);
     method public int describeContents();
@@ -1325,6 +1385,20 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.DevicePolicyStringResource> CREATOR;
   }
 
+  public final class DpcAuthority extends android.app.admin.Authority {
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.DpcAuthority> CREATOR;
+  }
+
+  public final class EnforcingAdmin implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public android.app.admin.Authority getAuthority();
+    method @NonNull public String getPackageName();
+    method @NonNull public android.os.UserHandle getUserHandle();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.EnforcingAdmin> CREATOR;
+  }
+
   public final class FullyManagedDeviceProvisioningParams implements android.os.Parcelable {
     method public boolean canDeviceOwnerGrantSensorsPermissions();
     method public int describeContents();
@@ -1352,6 +1426,21 @@
     method @NonNull public android.app.admin.FullyManagedDeviceProvisioningParams.Builder setTimeZone(@Nullable String);
   }
 
+  public final class IntentFilterPolicyKey extends android.app.admin.PolicyKey {
+    method public int describeContents();
+    method @NonNull public android.content.IntentFilter getIntentFilter();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.IntentFilterPolicyKey> CREATOR;
+  }
+
+  public final class LockTaskPolicy implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getFlags();
+    method @NonNull public java.util.Set<java.lang.String> getPackages();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.LockTaskPolicy> CREATOR;
+  }
+
   public final class ManagedProfileProvisioningParams implements android.os.Parcelable {
     method public int describeContents();
     method @Nullable public android.accounts.Account getAccountToMigrate();
@@ -1377,6 +1466,39 @@
     method @NonNull public android.app.admin.ManagedProfileProvisioningParams.Builder setProfileName(@Nullable String);
   }
 
+  public final class NoArgsPolicyKey extends android.app.admin.PolicyKey {
+    method public int describeContents();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.NoArgsPolicyKey> CREATOR;
+  }
+
+  public final class PackagePermissionPolicyKey extends android.app.admin.PolicyKey {
+    method public int describeContents();
+    method @NonNull public String getPackageName();
+    method @NonNull public String getPermissionName();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.PackagePermissionPolicyKey> CREATOR;
+  }
+
+  public final class PackagePolicyKey extends android.app.admin.PolicyKey {
+    method public int describeContents();
+    method @NonNull public String getPackageName();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.PackagePolicyKey> CREATOR;
+  }
+
+  public abstract class PolicyKey implements android.os.Parcelable {
+    method @NonNull public String getIdentifier();
+  }
+
+  public final class PolicyState<V> implements android.os.Parcelable {
+    method public int describeContents();
+    method @Nullable public V getCurrentResolvedPolicy();
+    method @NonNull public java.util.LinkedHashMap<android.app.admin.EnforcingAdmin,V> getPoliciesSetByAdmins();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.PolicyState<?>> CREATOR;
+  }
+
   public class ProvisioningException extends android.util.AndroidException {
     ctor public ProvisioningException(@NonNull Exception, int);
     ctor public ProvisioningException(@NonNull Exception, int, @Nullable String);
@@ -1391,6 +1513,12 @@
     field public static final int ERROR_UNKNOWN = 0; // 0x0
   }
 
+  public final class RoleAuthority extends android.app.admin.Authority {
+    method @NonNull public java.util.Set<java.lang.String> getRoles();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.RoleAuthority> CREATOR;
+  }
+
   public class SecurityLog {
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURITY_LOG) public static int writeEvent(int, @NonNull java.lang.Object...);
   }
@@ -1405,6 +1533,11 @@
     method public int getType();
   }
 
+  public final class UnknownAuthority extends android.app.admin.Authority {
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.UnknownAuthority> CREATOR;
+  }
+
 }
 
 package android.app.ambientcontext {
@@ -4896,8 +5029,8 @@
 
   public final class VirtualNavigationTouchpadConfig extends android.hardware.input.VirtualInputDeviceConfig implements android.os.Parcelable {
     method public int describeContents();
-    method public int getHeight();
-    method public int getWidth();
+    method @IntRange(from=1) public int getHeight();
+    method @IntRange(from=1) public int getWidth();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.hardware.input.VirtualNavigationTouchpadConfig> CREATOR;
   }
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 51e90d2..474e148 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -544,6 +544,7 @@
     method public void setDeviceOwnerType(@NonNull android.content.ComponentName, int);
     method @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_ADMINS) public void setNextOperationSafety(int, int);
     method @RequiresPermission(anyOf={android.Manifest.permission.MARK_DEVICE_ORGANIZATION_OWNED, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}, conditional=true) public void setProfileOwnerOnOrganizationOwnedDevice(@NonNull android.content.ComponentName, boolean);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public boolean triggerDevicePolicyEngineMigration(boolean);
     field public static final String ACTION_DATA_SHARING_RESTRICTION_APPLIED = "android.app.action.DATA_SHARING_RESTRICTION_APPLIED";
     field public static final String ACTION_DEVICE_POLICY_CONSTANTS_CHANGED = "android.app.action.DEVICE_POLICY_CONSTANTS_CHANGED";
     field public static final int DEVICE_OWNER_TYPE_DEFAULT = 0; // 0x0
@@ -592,10 +593,49 @@
     field @Deprecated public static final int STATUS_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER = 14; // 0xe
   }
 
+  public final class FlagUnion extends android.app.admin.ResolutionMechanism<java.lang.Integer> {
+    method public int describeContents();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.FlagUnion> CREATOR;
+  }
+
+  public final class MostRecent<V> extends android.app.admin.ResolutionMechanism<V> {
+    ctor public MostRecent();
+    method public int describeContents();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.MostRecent> CREATOR;
+  }
+
+  public final class MostRestrictive<V> extends android.app.admin.ResolutionMechanism<V> {
+    method public int describeContents();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.MostRestrictive<?>> CREATOR;
+  }
+
+  public final class PolicyState<V> implements android.os.Parcelable {
+    method @NonNull public android.app.admin.ResolutionMechanism<V> getResolutionMechanism();
+  }
+
+  public abstract class ResolutionMechanism<V> implements android.os.Parcelable {
+  }
+
   public static final class SecurityLog.SecurityEvent implements android.os.Parcelable {
     ctor public SecurityLog.SecurityEvent(long, byte[]);
   }
 
+  public final class StringSetUnion extends android.app.admin.ResolutionMechanism<java.util.Set<java.lang.String>> {
+    ctor public StringSetUnion();
+    method public int describeContents();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.StringSetUnion> CREATOR;
+  }
+
+  public final class TopPriority<V> extends android.app.admin.ResolutionMechanism<V> {
+    method public int describeContents();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.TopPriority<?>> CREATOR;
+  }
+
   public final class UnsafeStateException extends java.lang.IllegalStateException implements android.os.Parcelable {
     ctor public UnsafeStateException(int, int);
     method public int getOperation();
@@ -919,7 +959,7 @@
     method public boolean isQuietModeEnabled();
     method public boolean isRestricted();
     method public boolean supportsSwitchTo();
-    method public boolean supportsSwitchToByUser();
+    method @Deprecated public boolean supportsSwitchToByUser();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.UserInfo> CREATOR;
     field public static final int FLAG_ADMIN = 2; // 0x2
@@ -993,6 +1033,22 @@
 
 package android.credentials.ui {
 
+  public final class AuthenticationEntry implements android.os.Parcelable {
+    ctor public AuthenticationEntry(@NonNull String, @NonNull String, @NonNull android.app.slice.Slice, int);
+    ctor public AuthenticationEntry(@NonNull String, @NonNull String, @NonNull android.app.slice.Slice, int, @NonNull android.content.Intent);
+    method public int describeContents();
+    method @Nullable public android.content.Intent getFrameworkExtrasIntent();
+    method @NonNull public String getKey();
+    method @NonNull public android.app.slice.Slice getSlice();
+    method @NonNull public int getStatus();
+    method @NonNull public String getSubkey();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.credentials.ui.AuthenticationEntry> CREATOR;
+    field public static final int STATUS_LOCKED = 0; // 0x0
+    field public static final int STATUS_UNLOCKED_BUT_EMPTY_LESS_RECENT = 1; // 0x1
+    field public static final int STATUS_UNLOCKED_BUT_EMPTY_MOST_RECENT = 2; // 0x2
+  }
+
   public final class CreateCredentialProviderData extends android.credentials.ui.ProviderData implements android.os.Parcelable {
     ctor public CreateCredentialProviderData(@NonNull String, @NonNull java.util.List<android.credentials.ui.Entry>, @Nullable android.credentials.ui.Entry);
     method @Nullable public android.credentials.ui.Entry getRemoteEntry();
@@ -1024,15 +1080,12 @@
     method @NonNull public String getSubkey();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.credentials.ui.Entry> CREATOR;
-    field @NonNull public static final String EXTRA_ENTRY_AUTHENTICATION_ACTION = "android.credentials.ui.extra.ENTRY_AUTHENTICATION_ACTION";
-    field @NonNull public static final String EXTRA_ENTRY_LIST_ACTION_CHIP = "android.credentials.ui.extra.ENTRY_LIST_ACTION_CHIP";
-    field @NonNull public static final String EXTRA_ENTRY_LIST_CREDENTIAL = "android.credentials.ui.extra.ENTRY_LIST_CREDENTIAL";
   }
 
   public final class GetCredentialProviderData extends android.credentials.ui.ProviderData implements android.os.Parcelable {
-    ctor public GetCredentialProviderData(@NonNull String, @NonNull java.util.List<android.credentials.ui.Entry>, @NonNull java.util.List<android.credentials.ui.Entry>, @NonNull java.util.List<android.credentials.ui.Entry>, @Nullable android.credentials.ui.Entry);
+    ctor public GetCredentialProviderData(@NonNull String, @NonNull java.util.List<android.credentials.ui.Entry>, @NonNull java.util.List<android.credentials.ui.Entry>, @NonNull java.util.List<android.credentials.ui.AuthenticationEntry>, @Nullable android.credentials.ui.Entry);
     method @NonNull public java.util.List<android.credentials.ui.Entry> getActionChips();
-    method @NonNull public java.util.List<android.credentials.ui.Entry> getAuthenticationEntries();
+    method @NonNull public java.util.List<android.credentials.ui.AuthenticationEntry> getAuthenticationEntries();
     method @NonNull public java.util.List<android.credentials.ui.Entry> getCredentialEntries();
     method @Nullable public android.credentials.ui.Entry getRemoteEntry();
     field @NonNull public static final android.os.Parcelable.Creator<android.credentials.ui.GetCredentialProviderData> CREATOR;
@@ -1042,7 +1095,7 @@
     ctor public GetCredentialProviderData.Builder(@NonNull String);
     method @NonNull public android.credentials.ui.GetCredentialProviderData build();
     method @NonNull public android.credentials.ui.GetCredentialProviderData.Builder setActionChips(@NonNull java.util.List<android.credentials.ui.Entry>);
-    method @NonNull public android.credentials.ui.GetCredentialProviderData.Builder setAuthenticationEntries(@NonNull java.util.List<android.credentials.ui.Entry>);
+    method @NonNull public android.credentials.ui.GetCredentialProviderData.Builder setAuthenticationEntries(@NonNull java.util.List<android.credentials.ui.AuthenticationEntry>);
     method @NonNull public android.credentials.ui.GetCredentialProviderData.Builder setCredentialEntries(@NonNull java.util.List<android.credentials.ui.Entry>);
     method @NonNull public android.credentials.ui.GetCredentialProviderData.Builder setRemoteEntry(@Nullable android.credentials.ui.Entry);
   }
@@ -3776,6 +3829,7 @@
     method @NonNull public android.window.WindowContainerTransaction setHidden(@NonNull android.window.WindowContainerToken, boolean);
     method @NonNull public android.window.WindowContainerTransaction setLaunchAdjacentFlagRoot(@NonNull android.window.WindowContainerToken);
     method @NonNull public android.window.WindowContainerTransaction setLaunchRoot(@NonNull android.window.WindowContainerToken, @Nullable int[], @Nullable int[]);
+    method @NonNull public android.window.WindowContainerTransaction setRelativeBounds(@NonNull android.window.WindowContainerToken, @NonNull android.graphics.Rect);
     method @NonNull public android.window.WindowContainerTransaction setScreenSizeDp(@NonNull android.window.WindowContainerToken, int, int);
     method @NonNull public android.window.WindowContainerTransaction setSmallestScreenWidthDp(@NonNull android.window.WindowContainerToken, int);
     method @NonNull public android.window.WindowContainerTransaction setWindowingMode(@NonNull android.window.WindowContainerToken, int);
diff --git a/core/java/android/accessibilityservice/MagnificationConfig.java b/core/java/android/accessibilityservice/MagnificationConfig.java
index 486dc50..5019aee 100644
--- a/core/java/android/accessibilityservice/MagnificationConfig.java
+++ b/core/java/android/accessibilityservice/MagnificationConfig.java
@@ -107,7 +107,7 @@
      * Returns the activated state of the controlling magnifier. The controlling magnifier can be
      * activated even if the scale returned by {@link MagnificationConfig#getScale()} equals to 1.0.
      *
-     * @return {@code true} if the magnifier is showing on screen,
+     * @return {@code true} if the magnifier is activated and showing on screen,
      *         {@code false} otherwise.
      */
     public boolean isActivated() {
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index eb57b3d..5496191 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -109,6 +109,8 @@
 import android.net.Proxy;
 import android.net.TrafficStats;
 import android.net.Uri;
+import android.nfc.NfcFrameworkInitializer;
+import android.nfc.NfcServiceManager;
 import android.os.AsyncTask;
 import android.os.Binder;
 import android.os.BluetoothServiceManager;
@@ -786,10 +788,10 @@
     static final class ReceiverData extends BroadcastReceiver.PendingResult {
         public ReceiverData(Intent intent, int resultCode, String resultData, Bundle resultExtras,
                 boolean ordered, boolean sticky, boolean assumeDelivered, IBinder token,
-                int sendingUser, int sentFromUid, String sentFromPackage) {
+                int sendingUser, int sendingUid, String sendingPackage) {
             super(resultCode, resultData, resultExtras, TYPE_COMPONENT, ordered, sticky,
-                    assumeDelivered, token, sendingUser, intent.getFlags(), sentFromUid,
-                    sentFromPackage);
+                    assumeDelivered, token, sendingUser, intent.getFlags(), sendingUid,
+                    sendingPackage);
             this.intent = intent;
         }
 
@@ -1045,11 +1047,11 @@
         public final void scheduleReceiver(Intent intent, ActivityInfo info,
                 CompatibilityInfo compatInfo, int resultCode, String data, Bundle extras,
                 boolean ordered, boolean assumeDelivered, int sendingUser, int processState,
-                int sentFromUid, String sentFromPackage) {
+                int sendingUid, String sendingPackage) {
             updateProcessState(processState, false);
             ReceiverData r = new ReceiverData(intent, resultCode, data, extras,
                     ordered, false, assumeDelivered, mAppThread.asBinder(), sendingUser,
-                    sentFromUid, sentFromPackage);
+                    sendingUid, sendingPackage);
             r.info = info;
             sendMessage(H.RECEIVER, r);
         }
@@ -1061,12 +1063,12 @@
                     scheduleRegisteredReceiver(r.receiver, r.intent,
                             r.resultCode, r.data, r.extras, r.ordered, r.sticky,
                             r.assumeDelivered, r.sendingUser, r.processState,
-                            r.sentFromUid, r.sentFromPackage);
+                            r.sendingUid, r.sendingPackage);
                 } else {
                     scheduleReceiver(r.intent, r.activityInfo, r.compatInfo,
                             r.resultCode, r.data, r.extras, r.sync,
                             r.assumeDelivered, r.sendingUser, r.processState,
-                            r.sentFromUid, r.sentFromPackage);
+                            r.sendingUid, r.sendingPackage);
                 }
             }
         }
@@ -1297,7 +1299,7 @@
         public void scheduleRegisteredReceiver(IIntentReceiver receiver, Intent intent,
                 int resultCode, String dataStr, Bundle extras, boolean ordered,
                 boolean sticky, boolean assumeDelivered, int sendingUser, int processState,
-                int sentFromUid, String sentFromPackage)
+                int sendingUid, String sendingPackage)
                 throws RemoteException {
             updateProcessState(processState, false);
 
@@ -1308,16 +1310,16 @@
             if (receiver instanceof LoadedApk.ReceiverDispatcher.InnerReceiver) {
                 ((LoadedApk.ReceiverDispatcher.InnerReceiver) receiver).performReceive(intent,
                         resultCode, dataStr, extras, ordered, sticky, assumeDelivered, sendingUser,
-                        sentFromUid, sentFromPackage);
+                        sendingUid, sendingPackage);
             } else {
                 if (!assumeDelivered) {
                     Log.wtf(TAG, "scheduleRegisteredReceiver() called for " + receiver
                             + " and " + intent + " without mechanism to finish delivery");
                 }
-                if (sentFromUid != Process.INVALID_UID || sentFromPackage != null) {
+                if (sendingUid != Process.INVALID_UID || sendingPackage != null) {
                     Log.wtf(TAG,
                             "scheduleRegisteredReceiver() called for " + receiver + " and " + intent
-                                    + " from " + sentFromPackage + " (UID: " + sentFromUid
+                                    + " from " + sendingPackage + " (UID: " + sendingUid
                                     + ") without mechanism to propagate the sender's identity");
                 }
                 receiver.performReceive(intent, resultCode, dataStr, extras, ordered, sticky,
@@ -8135,6 +8137,7 @@
         BluetoothFrameworkInitializer.setBluetoothServiceManager(new BluetoothServiceManager());
         BluetoothFrameworkInitializer.setBinderCallsStatsInitializer(context -> {
             BinderCallsStats.startForBluetooth(context); });
+        NfcFrameworkInitializer.setNfcServiceManager(new NfcServiceManager());
     }
 
     private void purgePendingResources() {
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 7d40a22..8263a4e3 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1355,11 +1355,12 @@
             AppProtoEnums.APP_OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO;
 
     /**
-     * App can schedule long running jobs.
+     * App can schedule user-initiated jobs.
      *
      * @hide
      */
-    public static final int OP_RUN_LONG_JOBS = AppProtoEnums.APP_OP_RUN_LONG_JOBS;
+    public static final int OP_RUN_USER_INITIATED_JOBS =
+            AppProtoEnums.APP_OP_RUN_USER_INITIATED_JOBS;
 
     /**
      * Notify apps that they have been granted URI permission photos
@@ -1965,11 +1966,11 @@
             "android:receive_explicit_user_interaction_audio";
 
     /**
-     * App can schedule long running jobs.
+     * App can schedule user-initiated jobs.
      *
      * @hide
      */
-    public static final String OPSTR_RUN_LONG_JOBS = "android:run_long_jobs";
+    public static final String OPSTR_RUN_USER_INITIATED_JOBS = "android:run_user_initiated_jobs";
 
     /**
      * Prevent an app from being placed into app standby buckets.
@@ -2151,7 +2152,7 @@
             OP_SCHEDULE_EXACT_ALARM,
             OP_MANAGE_MEDIA,
             OP_TURN_SCREEN_ON,
-            OP_RUN_LONG_JOBS,
+            OP_RUN_USER_INITIATED_JOBS,
             OP_READ_MEDIA_VISUAL_USER_SELECTED,
             OP_FOREGROUND_SERVICE_SPECIAL_USE,
             OP_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD,
@@ -2537,8 +2538,9 @@
                 OPSTR_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO,
                 "RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO").setDefaultMode(
                 AppOpsManager.MODE_ALLOWED).build(),
-        new AppOpInfo.Builder(OP_RUN_LONG_JOBS, OPSTR_RUN_LONG_JOBS, "RUN_LONG_JOBS")
-                .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_RUN_USER_INITIATED_JOBS, OPSTR_RUN_USER_INITIATED_JOBS,
+                "RUN_USER_INITIATED_JOBS").setDefaultMode(AppOpsManager.MODE_ALLOWED)
+                .build(),
             new AppOpInfo.Builder(OP_READ_MEDIA_VISUAL_USER_SELECTED,
                     OPSTR_READ_MEDIA_VISUAL_USER_SELECTED, "READ_MEDIA_VISUAL_USER_SELECTED")
                     .setPermission(Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED)
diff --git a/core/java/android/app/ForegroundServiceTypePolicy.java b/core/java/android/app/ForegroundServiceTypePolicy.java
index 5ef29e4..a7824a8 100644
--- a/core/java/android/app/ForegroundServiceTypePolicy.java
+++ b/core/java/android/app/ForegroundServiceTypePolicy.java
@@ -107,9 +107,8 @@
      *
      * @hide
      */
-    // TODO (b/254661666): Change to @EnabledAfter(T)
     @ChangeId
-    @Disabled
+    @EnabledAfter(targetSdkVersion = android.os.Build.VERSION_CODES.TIRAMISU)
     @Overridable
     public static final long FGS_TYPE_NONE_DISABLED_CHANGE_ID = 255038118L;
 
@@ -142,9 +141,8 @@
      *
      * @hide
      */
-    // TODO (b/254661666): Change to @EnabledAfter(T)
     @ChangeId
-    @Disabled
+    @EnabledAfter(targetSdkVersion = android.os.Build.VERSION_CODES.TIRAMISU)
     @Overridable
     public static final long FGS_TYPE_PERMISSION_CHANGE_ID = 254662522L;
 
@@ -286,10 +284,7 @@
             new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
                 new RegularPermission(Manifest.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION)
             }, true),
-            new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
-                new RegularPermission(Manifest.permission.CAPTURE_VIDEO_OUTPUT),
-                new AppOpPermission(AppOpsManager.OP_PROJECT_MEDIA)
-            }, false)
+            null
     );
 
     /**
@@ -1058,7 +1053,7 @@
             if (policy.isTypeDisabled(callerUid)) {
                 return FGS_TYPE_POLICY_CHECK_DISABLED;
             }
-            int permissionResult = PERMISSION_DENIED;
+            int permissionResult = PERMISSION_GRANTED;
             // Do we have the permission to start FGS with this type.
             if (policy.mAllOfPermissions != null) {
                 permissionResult = policy.mAllOfPermissions.checkPermissions(context,
diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java
index 88c5064..be3d5a6 100644
--- a/core/java/android/app/KeyguardManager.java
+++ b/core/java/android/app/KeyguardManager.java
@@ -1075,6 +1075,46 @@
         return response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK;
     }
 
+    /** Starts a session to verify lockscreen credentials provided by a remote device.
+     *
+     * The session and corresponding public key will be removed when
+     * {@code validateRemoteLockScreen} provides a correct guess or after 10 minutes of inactivity.
+     *
+     * @return information necessary to perform remote lock screen credentials check, including
+
+     * short lived public key used to send encrypted guess and lock screen type.
+     *
+     * @throws IllegalStateException if lock screen is not set
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.CHECK_REMOTE_LOCKSCREEN)
+    @NonNull
+    public StartLockscreenValidationRequest startRemoteLockscreenValidation() {
+        return mLockPatternUtils.startRemoteLockscreenValidation();
+    }
+
+    /**
+     * Verifies credentials guess from a remote device.
+     *
+     * <p>Secret must be encrypted using {@code SecureBox} library
+     * with public key from {@code StartLockscreenValidationRequest}
+     * and header set to {@code "encrypted_remote_credentials"} in UTF-8 encoding.
+     *
+     * @throws IllegalStateException if there is no active lock screen validation session or
+     * there was a decryption error.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.CHECK_REMOTE_LOCKSCREEN)
+    @NonNull
+    public RemoteLockscreenValidationResult validateRemoteLockscreen(
+            @NonNull byte[] encryptedCredential) {
+        return mLockPatternUtils.validateRemoteLockscreen(encryptedCredential);
+    }
+
     private LockscreenCredential createLockscreenCredential(
             @LockTypes int lockType, @Nullable byte[] password) {
         if (password == null) {
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index c13da0b..dd6b8b5 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -1683,13 +1683,13 @@
                 performReceive(intent, resultCode, data, extras, ordered, sticky,
                         BroadcastReceiver.PendingResult.guessAssumeDelivered(
                                 BroadcastReceiver.PendingResult.TYPE_REGISTERED, ordered),
-                        sendingUser, /*sentFromUid=*/ Process.INVALID_UID,
-                        /*sentFromPackage=*/ null);
+                        sendingUser, /*sendingUid=*/ Process.INVALID_UID,
+                        /*sendingPackage=*/ null);
             }
 
             public void performReceive(Intent intent, int resultCode, String data,
                     Bundle extras, boolean ordered, boolean sticky, boolean assumeDelivered,
-                    int sendingUser, int sentFromUid, String sentFromPackage) {
+                    int sendingUser, int sendingUid, String sendingPackage) {
                 final LoadedApk.ReceiverDispatcher rd;
                 if (intent == null) {
                     Log.wtf(TAG, "Null intent received");
@@ -1705,7 +1705,7 @@
                 if (rd != null) {
                     rd.performReceive(intent, resultCode, data, extras,
                             ordered, sticky, assumeDelivered, sendingUser,
-                            sentFromUid, sentFromPackage);
+                            sendingUid, sendingPackage);
                 } else if (!assumeDelivered) {
                     // The activity manager dispatched a broadcast to a registered
                     // receiver in this process, but before it could be delivered the
@@ -1746,11 +1746,11 @@
 
             public Args(Intent intent, int resultCode, String resultData, Bundle resultExtras,
                     boolean ordered, boolean sticky, boolean assumeDelivered, int sendingUser,
-                    int sentFromUid, String sentFromPackage) {
+                    int sendingUid, String sendingPackage) {
                 super(resultCode, resultData, resultExtras,
                         mRegistered ? TYPE_REGISTERED : TYPE_UNREGISTERED, ordered,
                         sticky, assumeDelivered, mAppThread.asBinder(), sendingUser,
-                        intent.getFlags(), sentFromUid, sentFromPackage);
+                        intent.getFlags(), sendingUid, sendingPackage);
                 mCurIntent = intent;
             }
 
@@ -1874,9 +1874,9 @@
 
         public void performReceive(Intent intent, int resultCode, String data,
                 Bundle extras, boolean ordered, boolean sticky, boolean assumeDelivered,
-                int sendingUser, int sentFromUid, String sentFromPackage) {
+                int sendingUser, int sendingUid, String sendingPackage) {
             final Args args = new Args(intent, resultCode, data, extras, ordered,
-                    sticky, assumeDelivered, sendingUser, sentFromUid, sentFromPackage);
+                    sticky, assumeDelivered, sendingUser, sendingUid, sendingPackage);
             if (intent == null) {
                 Log.wtf(TAG, "Null intent received");
             } else {
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index b86b09d..9974e29 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -6201,10 +6201,8 @@
         private RemoteViews generateActionButton(Action action, boolean emphasizedMode,
                 StandardTemplateParams p) {
             final boolean tombstone = (action.actionIntent == null);
-            RemoteViews button = new BuilderRemoteViews(mContext.getApplicationInfo(),
-                    emphasizedMode ? getEmphasizedActionLayoutResource()
-                            : tombstone ? getActionTombstoneLayoutResource()
-                                    : getActionLayoutResource());
+            final RemoteViews button = new BuilderRemoteViews(mContext.getApplicationInfo(),
+                    getActionButtonLayoutResource(emphasizedMode, tombstone));
             if (!tombstone) {
                 button.setOnClickPendingIntent(R.id.action0, action.actionIntent);
             }
@@ -6216,6 +6214,12 @@
                 // change the background bgColor
                 CharSequence title = action.title;
                 int buttonFillColor = getColors(p).getSecondaryAccentColor();
+                if (tombstone) {
+                    buttonFillColor = setAlphaComponentByFloatDimen(mContext,
+                            ContrastColorUtil.resolveSecondaryColor(
+                                    mContext, getColors(p).getBackgroundColor(), mInNightMode),
+                            R.dimen.notification_action_disabled_container_alpha);
+                }
                 if (isLegacy()) {
                     title = ContrastColorUtil.clearColorSpans(title);
                 } else {
@@ -6231,8 +6235,14 @@
                     title = ensureColorSpanContrast(title, buttonFillColor);
                 }
                 button.setTextViewText(R.id.action0, processTextSpans(title));
-                final int textColor = ContrastColorUtil.resolvePrimaryColor(mContext,
+                int textColor = ContrastColorUtil.resolvePrimaryColor(mContext,
                         buttonFillColor, mInNightMode);
+                if (tombstone) {
+                    textColor = setAlphaComponentByFloatDimen(mContext,
+                            ContrastColorUtil.resolveSecondaryColor(
+                                    mContext, getColors(p).getBackgroundColor(), mInNightMode),
+                            R.dimen.notification_action_disabled_content_alpha);
+                }
                 button.setTextColor(R.id.action0, textColor);
                 // We only want about 20% alpha for the ripple
                 final int rippleColor = (textColor & 0x00ffffff) | 0x33000000;
@@ -6262,6 +6272,26 @@
             return button;
         }
 
+        private int getActionButtonLayoutResource(boolean emphasizedMode, boolean tombstone) {
+            if (emphasizedMode) {
+                return tombstone ? getEmphasizedTombstoneActionLayoutResource()
+                        : getEmphasizedActionLayoutResource();
+            } else {
+                return tombstone ? getActionTombstoneLayoutResource()
+                        : getActionLayoutResource();
+            }
+        }
+
+        /**
+         * Set the alpha component of {@code color} to be {@code alphaDimenResId}.
+         */
+        private static int setAlphaComponentByFloatDimen(Context context, @ColorInt int color,
+                @DimenRes int alphaDimenResId) {
+            final TypedValue alphaValue = new TypedValue();
+            context.getResources().getValue(alphaDimenResId, alphaValue, true);
+            return ColorUtils.setAlphaComponent(color, Math.round(alphaValue.getFloat() * 255));
+        }
+
         /**
          * Extract the color from a full-length span from the text.
          *
@@ -6741,6 +6771,10 @@
             return R.layout.notification_material_action_emphasized;
         }
 
+        private int getEmphasizedTombstoneActionLayoutResource() {
+            return R.layout.notification_material_action_emphasized_tombstone;
+        }
+
         private int getActionTombstoneLayoutResource() {
             return R.layout.notification_material_action_tombstone;
         }
diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS
index 6206f31..824c7cc 100644
--- a/core/java/android/app/OWNERS
+++ b/core/java/android/app/OWNERS
@@ -4,12 +4,12 @@
 
 # ActivityManager
 per-file ActivityManager* = file:/services/core/java/com/android/server/am/OWNERS
+per-file ApplicationStartInfo* = file:/services/core/java/com/android/server/am/OWNERS
 per-file ApplicationErrorReport* = file:/services/core/java/com/android/server/am/OWNERS
 per-file ApplicationExitInfo* = file:/services/core/java/com/android/server/am/OWNERS
 per-file Application.java = file:/services/core/java/com/android/server/am/OWNERS
 per-file ApplicationLoaders.java = file:/services/core/java/com/android/server/am/OWNERS
 per-file ApplicationThreadConstants.java = file:/services/core/java/com/android/server/am/OWNERS
-per-file BroadcastOptions.java = file:/services/core/java/com/android/server/am/OWNERS
 per-file ContentProviderHolder* = file:/services/core/java/com/android/server/am/OWNERS
 per-file *ForegroundService* = file:/services/core/java/com/android/server/am/OWNERS
 per-file IActivityController.aidl = file:/services/core/java/com/android/server/am/OWNERS
@@ -50,6 +50,10 @@
 # Backup and Restore
 per-file IBackupAgent.aidl = file:/services/backup/OWNERS
 
+# Broadcasts
+per-file Broadcast* = file:/BROADCASTS_OWNERS
+per-file ReceiverInfo* = file:/BROADCASTS_OWNERS
+
 # LocaleManager
 per-file *Locale* = file:/services/core/java/com/android/server/locales/OWNERS
 
diff --git a/core/java/android/app/ReceiverInfo.aidl b/core/java/android/app/ReceiverInfo.aidl
index 7364d0f..6916f71 100644
--- a/core/java/android/app/ReceiverInfo.aidl
+++ b/core/java/android/app/ReceiverInfo.aidl
@@ -38,8 +38,8 @@
     int sendingUser;
     int processState;
     int resultCode;
-    int sentFromUid = -1;
-    String sentFromPackage;
+    int sendingUid = -1;
+    String sendingPackage;
 
     /**
      * True if this instance represents a registered receiver and false if this instance
diff --git a/core/java/android/app/RemoteLockscreenValidationResult.java b/core/java/android/app/RemoteLockscreenValidationResult.java
index 4f15be2..0245f8c 100644
--- a/core/java/android/app/RemoteLockscreenValidationResult.java
+++ b/core/java/android/app/RemoteLockscreenValidationResult.java
@@ -19,6 +19,7 @@
 import android.annotation.DurationMillisLong;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -29,6 +30,7 @@
  *
  * @hide
  */
+@SystemApi
 public final class RemoteLockscreenValidationResult implements Parcelable {
 
     /**
diff --git a/core/java/android/app/StartLockscreenValidationRequest.java b/core/java/android/app/StartLockscreenValidationRequest.java
index 69c268bcb..e818195 100644
--- a/core/java/android/app/StartLockscreenValidationRequest.java
+++ b/core/java/android/app/StartLockscreenValidationRequest.java
@@ -17,6 +17,7 @@
 package android.app;
 
 import android.annotation.NonNull;
+import android.annotation.SystemApi;
 import android.app.KeyguardManager.LockTypes;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -28,6 +29,7 @@
  *
  * @hide
  */
+@SystemApi
 public final class StartLockscreenValidationRequest implements Parcelable {
 
     @LockTypes
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index 9e31011..f74be22 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -940,7 +940,7 @@
      * @param tileLabel label of the tile to show to the user.
      * @param icon icon to use in the tile shown to the user.
      * @param resultExecutor an executor to run the callback on
-     * @param resultCallback callback to indicate the {@link RequestResult}.
+     * @param resultCallback callback to indicate the result of the request.
      *
      * @see android.service.quicksettings.TileService
      */
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 0365f8c..64538ec 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -157,7 +157,7 @@
 import android.net.wifi.WifiFrameworkInitializer;
 import android.net.wifi.nl80211.WifiNl80211Manager;
 import android.net.wifi.sharedconnectivity.app.SharedConnectivityManager;
-import android.nfc.NfcManager;
+import android.nfc.NfcFrameworkInitializer;
 import android.ondevicepersonalization.OnDevicePersonalizationFrameworkInitializer;
 import android.os.BatteryManager;
 import android.os.BatteryStats;
@@ -484,13 +484,6 @@
                 return new BatteryManager(ctx, stats, registrar);
             }});
 
-        registerService(Context.NFC_SERVICE, NfcManager.class,
-                new CachedServiceFetcher<NfcManager>() {
-            @Override
-            public NfcManager createService(ContextImpl ctx) {
-                return new NfcManager(ctx);
-            }});
-
         registerService(Context.DROPBOX_SERVICE, DropBoxManager.class,
                 new CachedServiceFetcher<DropBoxManager>() {
             @Override
@@ -1589,6 +1582,7 @@
             JobSchedulerFrameworkInitializer.registerServiceWrappers();
             BlobStoreManagerFrameworkInitializer.initialize();
             BluetoothFrameworkInitializer.registerServiceWrappers();
+            NfcFrameworkInitializer.registerServiceWrappers();
             TelephonyFrameworkInitializer.registerServiceWrappers();
             AppSearchManagerFrameworkInitializer.initialize();
             HealthServicesInitializer.registerServiceWrappers();
diff --git a/core/java/android/app/admin/Authority.java b/core/java/android/app/admin/Authority.java
new file mode 100644
index 0000000..52f79cf
--- /dev/null
+++ b/core/java/android/app/admin/Authority.java
@@ -0,0 +1,55 @@
+/*
+ * 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 android.app.admin;
+
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.os.Parcelable;
+
+/**
+ * Abstract class used to identify the authority of the {@link EnforcingAdmin}.
+ *
+ * @hide
+ */
+// This is ok as the constructor is package-protected and all subclasses have implemented
+// Parcelable.
+@SuppressLint({"ParcelNotFinal", "ParcelCreator"})
+@SystemApi
+public abstract class Authority implements Parcelable {
+
+    /**
+     * @hide
+     */
+    protected Authority() {}
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (this == o) return true;
+        return o != null && getClass() == o.getClass();
+    }
+
+    @Override
+    public int hashCode() {
+        return 0;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+}
diff --git a/core/java/android/app/admin/BooleanPolicyValue.java b/core/java/android/app/admin/BooleanPolicyValue.java
new file mode 100644
index 0000000..aa6f4a4
--- /dev/null
+++ b/core/java/android/app/admin/BooleanPolicyValue.java
@@ -0,0 +1,79 @@
+/*
+ * 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 android.app.admin;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+
+import java.util.Objects;
+
+/**
+ * @hide
+ */
+public final class BooleanPolicyValue extends PolicyValue<Boolean> {
+
+    public BooleanPolicyValue(boolean value) {
+        super(value);
+    }
+
+    private BooleanPolicyValue(Parcel source) {
+        this(source.readBoolean());
+    }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        BooleanPolicyValue other = (BooleanPolicyValue) o;
+        return Objects.equals(getValue(), other.getValue());
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(getValue());
+    }
+
+    @Override
+    public String toString() {
+        return "BooleanPolicyValue { mValue= " + getValue() + " }";
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeBoolean(getValue());
+    }
+
+    @NonNull
+    public static final Creator<BooleanPolicyValue> CREATOR =
+            new Creator<BooleanPolicyValue>() {
+                @Override
+                public BooleanPolicyValue createFromParcel(Parcel source) {
+                    return new BooleanPolicyValue(source);
+                }
+
+                @Override
+                public BooleanPolicyValue[] newArray(int size) {
+                    return new BooleanPolicyValue[size];
+                }
+            };
+}
diff --git a/core/java/android/app/admin/BundlePolicyValue.java b/core/java/android/app/admin/BundlePolicyValue.java
new file mode 100644
index 0000000..f9653a4
--- /dev/null
+++ b/core/java/android/app/admin/BundlePolicyValue.java
@@ -0,0 +1,80 @@
+/*
+ * 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 android.app.admin;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Bundle;
+import android.os.Parcel;
+
+import java.util.Objects;
+
+/**
+ * @hide
+ */
+public final class BundlePolicyValue extends PolicyValue<Bundle> {
+
+    public BundlePolicyValue(Bundle value) {
+        super(value);
+    }
+
+    private BundlePolicyValue(Parcel source) {
+        this(source.readBundle());
+    }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        BundlePolicyValue other = (BundlePolicyValue) o;
+        return Objects.equals(getValue(), other.getValue());
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(getValue());
+    }
+
+    @Override
+    public String toString() {
+        return "BundlePolicyValue { mValue= " + getValue() + " }";
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeBundle(getValue());
+    }
+
+    @NonNull
+    public static final Creator<BundlePolicyValue> CREATOR =
+            new Creator<BundlePolicyValue>() {
+                @Override
+                public BundlePolicyValue createFromParcel(Parcel source) {
+                    return new BundlePolicyValue(source);
+                }
+
+                @Override
+                public BundlePolicyValue[] newArray(int size) {
+                    return new BundlePolicyValue[size];
+                }
+            };
+}
diff --git a/core/java/android/app/admin/ComponentNamePolicyValue.java b/core/java/android/app/admin/ComponentNamePolicyValue.java
new file mode 100644
index 0000000..635e582
--- /dev/null
+++ b/core/java/android/app/admin/ComponentNamePolicyValue.java
@@ -0,0 +1,80 @@
+/*
+ * 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 android.app.admin;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.os.Parcel;
+
+import java.util.Objects;
+
+/**
+ * @hide
+ */
+public final class ComponentNamePolicyValue extends PolicyValue<ComponentName> {
+
+    public ComponentNamePolicyValue(@NonNull ComponentName value) {
+        super(value);
+    }
+
+    private ComponentNamePolicyValue(Parcel source) {
+        this((ComponentName) source.readParcelable(ComponentName.class.getClassLoader()));
+    }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        ComponentNamePolicyValue other = (ComponentNamePolicyValue) o;
+        return Objects.equals(getValue(), other.getValue());
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(getValue());
+    }
+
+    @Override
+    public String toString() {
+        return "ComponentNamePolicyValue { mValue= " + getValue() + " }";
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeParcelable(getValue(), flags);
+    }
+
+    @NonNull
+    public static final Creator<ComponentNamePolicyValue> CREATOR =
+            new Creator<ComponentNamePolicyValue>() {
+                @Override
+                public ComponentNamePolicyValue createFromParcel(Parcel source) {
+                    return new ComponentNamePolicyValue(source);
+                }
+
+                @Override
+                public ComponentNamePolicyValue[] newArray(int size) {
+                    return new ComponentNamePolicyValue[size];
+                }
+            };
+}
diff --git a/core/java/android/app/admin/DeviceAdminAuthority.java b/core/java/android/app/admin/DeviceAdminAuthority.java
new file mode 100644
index 0000000..5d1ff11
--- /dev/null
+++ b/core/java/android/app/admin/DeviceAdminAuthority.java
@@ -0,0 +1,76 @@
+/*
+ * 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 android.app.admin;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+
+/**
+ * Class used to identify that authority of the {@link EnforcingAdmin} setting a policy is a non-DPC
+ * device admin.
+ *
+ * @hide
+ */
+@SystemApi
+public final class DeviceAdminAuthority extends Authority {
+
+    /**
+     * @hide
+     */
+    public static final DeviceAdminAuthority DEVICE_ADMIN_AUTHORITY = new DeviceAdminAuthority();
+
+    private DeviceAdminAuthority() {}
+
+    @Override
+    public String toString() {
+        return "DeviceAdminAuthority {}";
+    }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        return super.equals(o);
+    }
+
+    @Override
+    public int hashCode() {
+        return super.hashCode();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {}
+
+    @NonNull
+    public static final Creator<DeviceAdminAuthority> CREATOR =
+            new Creator<DeviceAdminAuthority>() {
+                @Override
+                public DeviceAdminAuthority createFromParcel(Parcel source) {
+                    return new DeviceAdminAuthority();
+                }
+
+                @Override
+                public DeviceAdminAuthority[] newArray(int size) {
+                    return new DeviceAdminAuthority[size];
+                }
+            };
+}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 3fe63d8..e7f6990 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -18,6 +18,7 @@
 
 import static android.Manifest.permission.INTERACT_ACROSS_USERS;
 import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+import static android.Manifest.permission.MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY;
 import static android.Manifest.permission.QUERY_ADMIN_POLICY;
 import static android.Manifest.permission.SET_TIME;
 import static android.Manifest.permission.SET_TIME_ZONE;
@@ -3887,6 +3888,14 @@
     public static final int EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS =  1;
 
     /**
+     * Allows an application to start an activity while running in the background.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION = 2;
+
+    /**
      * Exemptions to platform restrictions, given to an application through
      * {@link #setApplicationExemptions(String, Set)}.
      *
@@ -3894,7 +3903,8 @@
      */
     @IntDef(prefix = { "EXEMPT_FROM_"}, value = {
             EXEMPT_FROM_APP_STANDBY,
-            EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS
+            EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS,
+            EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ApplicationExemptionConstants {}
@@ -4020,18 +4030,8 @@
     /**
      * @hide
      */
-    public static final String PERMISSION_GRANT_POLICY_KEY = "permissionGrant";
+    public static final String PERMISSION_GRANT_POLICY = "permissionGrant";
 
-    // TODO: Expose this as SystemAPI once we add the query API
-    /**
-     * @hide
-     */
-    public static String PERMISSION_GRANT_POLICY(
-            @NonNull String packageName, @NonNull String permission) {
-        Objects.requireNonNull(packageName);
-        Objects.requireNonNull(permission);
-        return PERMISSION_GRANT_POLICY_KEY + "_" + packageName + "_" + permission;
-    }
 
     // TODO: Expose this as SystemAPI once we add the query API
     /**
@@ -4060,6 +4060,12 @@
      */
     public static final String PACKAGE_UNINSTALL_BLOCKED_POLICY = "packageUninstallBlocked";
 
+    // TODO: Expose this as SystemAPI once we add the query API
+    /**
+     * @hide
+     */
+    public static final String APPLICATION_RESTRICTIONS_POLICY = "applicationRestrictions";
+
     /**
      * This object is a single place to tack on invalidation and disable calls.  All
      * binder caches in this class derive from this Config, so all can be invalidated or
@@ -8488,7 +8494,8 @@
      * higher, to set whether auto time is required. If auto time is required, no user will be able
      * set the date and time and network date and time will be used.
      * <p>
-     * Note: if auto time is required the user can still manually set the time zone.
+     * Note: If auto time is required the user can still manually set the time zone. Staring from
+     * Android 11, if auto time is required, the user cannot manually set the time zone.
      * <p>
      * The calling device admin must be a device owner, or alternatively a profile owner from
      * Android 8.0 (API level 26) or higher. If it is not, a security exception will be thrown.
@@ -13485,18 +13492,24 @@
     }
 
     /**
-     * Called by the device owner (since API 26) or profile owner (since API 24) to set the name of
-     * the organization under management.
+     * Called by the device owner (since API 26) or profile owner (since API 24) or, starting from
+     * Android {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+     * holders of the permission
+     * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY} to set the
+     * name of the organization under management.
      *
-     * <p>If the organization name needs to be localized, it is the responsibility of the {@link
-     * DeviceAdminReceiver} to listen to the {@link Intent#ACTION_LOCALE_CHANGED} broadcast and set
-     * a new version of this string accordingly.
+     * <p>If the organization name needs to be localized, it is the responsibility of the caller
+     * to listen to the {@link Intent#ACTION_LOCALE_CHANGED} broadcast and set a new version of this
+     * string accordingly.
      *
-     * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated with or can be
+     * {@code null} if accessing with a permission without association with a DeviceAdminReceiver.
      * @param title The organization name or {@code null} to clear a previously set name.
-     * @throws SecurityException if {@code admin} is not a device or profile owner.
+     * @throws SecurityException if {@code admin} is not a device or profile owner or holder of the
+     * permission {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY}.
      */
-    public void setOrganizationName(@NonNull ComponentName admin, @Nullable CharSequence title) {
+    @RequiresPermission(MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY)
+    public void setOrganizationName(@Nullable ComponentName admin, @Nullable CharSequence title) {
         throwIfParentInstance("setOrganizationName");
         try {
             mService.setOrganizationName(admin, title);
@@ -13506,14 +13519,21 @@
     }
 
     /**
-     * Called by a profile owner of a managed profile to retrieve the name of the organization under
-     * management.
+     * Called by the device owner (since API 26) or profile owner (since API 24) or, starting from
+     * Android {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+     * holders of the permission
+     * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY
+     * to retrieve the name of the organization under management.
      *
-     * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated with or can be
+     * {@code null} if accessing with a permission without association with a DeviceAdminReceiver.
      * @return The organization name or {@code null} if none is set.
-     * @throws SecurityException if {@code admin} is not a profile owner.
+     * @throws SecurityException if {@code admin} if {@code admin} is not a device or profile
+     * owner or holder of the
+     * permission {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY}.
      */
-    public @Nullable CharSequence getOrganizationName(@NonNull ComponentName admin) {
+    @RequiresPermission(MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY)
+    public @Nullable CharSequence getOrganizationName(@Nullable ComponentName admin) {
         throwIfParentInstance("getOrganizationName");
         try {
             return mService.getOrganizationName(admin);
@@ -16299,4 +16319,47 @@
         }
         return false;
     }
+
+    /**
+     * Returns a {@link DevicePolicyState} object containing information about the current state
+     * of device policies (e.g. values set by different admins, info about the enforcing admins,
+     * resolved policy, etc).
+     *
+     * @hide
+     */
+    @SystemApi
+    @NonNull
+    @RequiresPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)
+    public DevicePolicyState getDevicePolicyState() {
+        if (mService != null) {
+            try {
+                return mService.getDevicePolicyState();
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Triggers the data migration of device policies for existing DPCs to the Device Policy Engine.
+     * If {@code forceMigration} is set to {@code true} it skips the prerequisite checks before
+     * triggering the migration.
+     *
+     * <p>Returns {@code true} if migration was completed successfully, {@code false} otherwise.
+     *
+     * @hide
+     */
+    @TestApi
+    @RequiresPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)
+    public boolean triggerDevicePolicyEngineMigration(boolean forceMigration) {
+        if (mService != null) {
+            try {
+                return mService.triggerDevicePolicyEngineMigration(forceMigration);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+        return false;
+    }
 }
diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java
index 1b5c196..8076615 100644
--- a/core/java/android/app/admin/DevicePolicyManagerInternal.java
+++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java
@@ -21,9 +21,11 @@
 import android.annotation.UserIdInt;
 import android.content.ComponentName;
 import android.content.Intent;
+import android.os.Bundle;
 import android.os.UserHandle;
 
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 /**
@@ -309,4 +311,10 @@
      * Returns whether the application exemptions feature flag is enabled.
      */
     public abstract boolean isApplicationExemptionsFlagEnabled();
+
+    /**
+    * Returns the application restrictions set by each admin for the given {@code packageName}.
+     */
+    public abstract Map<String, Bundle> getApplicationRestrictionsPerAdmin(
+            String packageName, int userId);
 }
diff --git a/core/java/android/app/admin/DevicePolicyState.aidl b/core/java/android/app/admin/DevicePolicyState.aidl
new file mode 100644
index 0000000..0eac33f
--- /dev/null
+++ b/core/java/android/app/admin/DevicePolicyState.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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 android.app.admin;
+
+parcelable DevicePolicyState;
\ No newline at end of file
diff --git a/core/java/android/app/admin/DevicePolicyState.java b/core/java/android/app/admin/DevicePolicyState.java
new file mode 100644
index 0000000..ee33b00
--- /dev/null
+++ b/core/java/android/app/admin/DevicePolicyState.java
@@ -0,0 +1,114 @@
+/*
+ * 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 android.app.admin;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.UserHandle;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * A class containing information about the current state of device policies (e.g. values set by
+ * different admins, info about the enforcing admins, resolved policy, etc).
+ *
+ * @hide
+ */
+@SystemApi
+public final class DevicePolicyState implements Parcelable {
+    private final Map<UserHandle, Map<PolicyKey, PolicyState<?>>> mPolicies;
+
+    /**
+     * @hide
+     */
+    public DevicePolicyState(Map<UserHandle, Map<PolicyKey, PolicyState<?>>> policies) {
+        mPolicies = Objects.requireNonNull(policies);
+    }
+
+    private DevicePolicyState(Parcel source) {
+        mPolicies = new HashMap<>();
+        int usersSize = source.readInt();
+        for (int i = 0; i < usersSize; i++) {
+            UserHandle userHandle = UserHandle.of(source.readInt());
+            mPolicies.put(userHandle, new HashMap<>());
+            int policiesSize = source.readInt();
+            for (int j = 0; j < policiesSize; j++) {
+                PolicyKey policyKey =
+                        source.readParcelable(PolicyKey.class.getClassLoader());
+                PolicyState<?> policyState =
+                        source.readParcelable(PolicyState.class.getClassLoader());
+                mPolicies.get(userHandle).put(policyKey, policyState);
+            }
+        }
+    }
+
+    /**
+     * Returns a {@link Map} of current policies for each {@link UserHandle}, note that users
+     * that do not have any policies set will not be included in the returned map.
+     *
+     * <p> If the device has global policies affecting all users, it will be returned under
+     * {@link UserHandle#ALL}.
+     */
+    @NonNull
+    public Map<UserHandle, Map<PolicyKey, PolicyState<?>>> getPoliciesForAllUsers() {
+        return mPolicies;
+    }
+
+    /**
+     * Returns a {@link Map} of current policies for the provided {@code user}, use
+     * {@link UserHandle#ALL} to get global policies affecting all users on the device.
+     */
+    @NonNull
+    public Map<PolicyKey, PolicyState<?>> getPoliciesForUser(@NonNull UserHandle user) {
+        return mPolicies.containsKey(user) ? mPolicies.get(user) : new HashMap<>();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mPolicies.size());
+        for (UserHandle user : mPolicies.keySet()) {
+            dest.writeInt(user.getIdentifier());
+            dest.writeInt(mPolicies.get(user).size());
+            for (PolicyKey key : mPolicies.get(user).keySet()) {
+                dest.writeParcelable(key, flags);
+                dest.writeParcelable(mPolicies.get(user).get(key), flags);
+            }
+        }
+    }
+
+    public static final @NonNull Parcelable.Creator<DevicePolicyState> CREATOR =
+            new Parcelable.Creator<DevicePolicyState>() {
+                @Override
+                public DevicePolicyState createFromParcel(Parcel source) {
+                    return new DevicePolicyState(source);
+                }
+
+                @Override
+                public DevicePolicyState[] newArray(int size) {
+                    return new DevicePolicyState[size];
+                }
+            };
+}
diff --git a/core/java/android/app/admin/DpcAuthority.java b/core/java/android/app/admin/DpcAuthority.java
new file mode 100644
index 0000000..72c16bc
--- /dev/null
+++ b/core/java/android/app/admin/DpcAuthority.java
@@ -0,0 +1,76 @@
+/*
+ * 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 android.app.admin;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+
+/**
+ * Class used to identify that authority of the {@link EnforcingAdmin} setting a policy is a DPC or
+ * an app acting as a DPC (e.g. delegate apps).
+ *
+ * @hide
+ */
+@SystemApi
+public final class DpcAuthority extends Authority {
+
+    /**
+     * @hide
+     */
+    public static final DpcAuthority DPC_AUTHORITY = new DpcAuthority();
+
+    private DpcAuthority() {}
+
+    @Override
+    public String toString() {
+        return "DpcAuthority {}";
+    }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        return super.equals(o);
+    }
+
+    @Override
+    public int hashCode() {
+        return super.hashCode();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {}
+
+    @NonNull
+    public static final Creator<DpcAuthority> CREATOR =
+            new Creator<DpcAuthority>() {
+                @Override
+                public DpcAuthority createFromParcel(Parcel source) {
+                    return new DpcAuthority();
+                }
+
+                @Override
+                public DpcAuthority[] newArray(int size) {
+                    return new DpcAuthority[size];
+                }
+            };
+}
diff --git a/core/java/android/app/admin/EnforcingAdmin.java b/core/java/android/app/admin/EnforcingAdmin.java
new file mode 100644
index 0000000..1786467
--- /dev/null
+++ b/core/java/android/app/admin/EnforcingAdmin.java
@@ -0,0 +1,128 @@
+/*
+ * 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 android.app.admin;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.UserHandle;
+
+import java.util.Objects;
+
+/**
+ * Class containing info about the admin enforcing a certain policy, e.g. its {@code packageName}
+ * and {@link Authority}.
+ *
+ * @hide
+ */
+@SystemApi
+public final class EnforcingAdmin implements Parcelable {
+    private final String mPackageName;
+    private final Authority mAuthority;
+    private final UserHandle mUserHandle;
+
+    /**
+     * @hide
+     */
+    public EnforcingAdmin(
+            @NonNull String packageName, @NonNull Authority authority,
+            @NonNull UserHandle userHandle) {
+        mPackageName = Objects.requireNonNull(packageName);
+        mAuthority = Objects.requireNonNull(authority);
+        mUserHandle = Objects.requireNonNull(userHandle);
+    }
+
+    private EnforcingAdmin(Parcel source) {
+        mPackageName = Objects.requireNonNull(source.readString());
+        mUserHandle = new UserHandle(source.readInt());
+        mAuthority = Objects.requireNonNull(
+                source.readParcelable(Authority.class.getClassLoader()));
+    }
+
+    /**
+     * Returns the {@link Authority} on which the admin is acting on, e.g. DPC, DeviceAdmin, etc.
+     */
+    @NonNull
+    public Authority getAuthority() {
+        return mAuthority;
+    }
+
+    /**
+     * Returns the package name of the admin.
+     */
+    @NonNull
+    public String getPackageName() {
+        return mPackageName;
+    }
+
+    /**
+     * Returns the {@link UserHandle} on which the admin is installed on.
+     */
+    @NonNull
+    public UserHandle getUserHandle() {
+        return mUserHandle;
+    }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        EnforcingAdmin other = (EnforcingAdmin) o;
+        return Objects.equals(mPackageName, other.mPackageName)
+                && Objects.equals(mAuthority, other.mAuthority)
+                && Objects.equals(mUserHandle, other.mUserHandle);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mPackageName, mAuthority, mUserHandle);
+    }
+
+    @Override
+    public String toString() {
+        return "EnforcingAdmin { mPackageName= " + mPackageName + ", mAuthority= " + mAuthority
+                + ", mUserHandle= " + mUserHandle + " }";
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString(mPackageName);
+        dest.writeInt(mUserHandle.getIdentifier());
+        dest.writeParcelable(mAuthority, flags);
+    }
+
+    @NonNull
+    public static final Parcelable.Creator<EnforcingAdmin> CREATOR =
+            new Parcelable.Creator<EnforcingAdmin>() {
+                @Override
+                public EnforcingAdmin createFromParcel(Parcel source) {
+                    return new EnforcingAdmin(source);
+                }
+
+                @Override
+                public EnforcingAdmin[] newArray(int size) {
+                    return new EnforcingAdmin[size];
+                }
+            };
+}
diff --git a/core/java/android/app/admin/FlagUnion.java b/core/java/android/app/admin/FlagUnion.java
new file mode 100644
index 0000000..be924d8
--- /dev/null
+++ b/core/java/android/app/admin/FlagUnion.java
@@ -0,0 +1,82 @@
+/*
+ * 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.app.admin;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Class to identify a union resolution mechanism for flag policies, it's used to resolve the
+ * enforced policy when being set by multiple admins (see
+ * {@link PolicyState#getResolutionMechanism()}).
+ *
+ * @hide
+ */
+@TestApi
+public final class FlagUnion extends ResolutionMechanism<Integer> {
+
+    /**
+     * @hide
+     */
+    public static final FlagUnion FLAG_UNION = new FlagUnion();
+
+    private FlagUnion(){};
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (this == o) return true;
+        return o != null && getClass() == o.getClass();
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(this);
+    }
+
+    @Override
+    public String toString() {
+        return "FlagUnion {}";
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {}
+
+    @NonNull
+    public static final Parcelable.Creator<FlagUnion> CREATOR =
+            new Parcelable.Creator<FlagUnion>() {
+                @Override
+                public FlagUnion createFromParcel(Parcel source) {
+                    return new FlagUnion();
+                }
+
+                @Override
+                public FlagUnion[] newArray(int size) {
+                    return new FlagUnion[size];
+                }
+            };
+
+}
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 1ed39c5..c86852a 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -53,6 +53,7 @@
 import android.security.keystore.ParcelableKeyGenParameterSpec;
 import android.telephony.data.ApnSetting;
 import com.android.internal.infra.AndroidFuture;
+import android.app.admin.DevicePolicyState;
 
 import java.util.List;
 
@@ -591,4 +592,8 @@
 
     void setManagedSubscriptionsPolicy(in ManagedSubscriptionsPolicy policy);
     ManagedSubscriptionsPolicy getManagedSubscriptionsPolicy();
+
+    DevicePolicyState getDevicePolicyState();
+
+    boolean triggerDevicePolicyEngineMigration(boolean forceMigration);
 }
diff --git a/core/java/android/app/admin/IntegerPolicyValue.java b/core/java/android/app/admin/IntegerPolicyValue.java
new file mode 100644
index 0000000..6fa6180
--- /dev/null
+++ b/core/java/android/app/admin/IntegerPolicyValue.java
@@ -0,0 +1,79 @@
+/*
+ * 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 android.app.admin;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+
+import java.util.Objects;
+
+/**
+ * @hide
+ */
+public final class IntegerPolicyValue extends PolicyValue<Integer> {
+
+    public IntegerPolicyValue(int value) {
+        super(value);
+    }
+
+    private IntegerPolicyValue(Parcel source) {
+        this(source.readInt());
+    }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        IntegerPolicyValue other = (IntegerPolicyValue) o;
+        return Objects.equals(getValue(), other.getValue());
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(getValue());
+    }
+
+    @Override
+    public String toString() {
+        return "IntegerPolicyValue { mValue= " + getValue() + " }";
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(getValue());
+    }
+
+    @NonNull
+    public static final Creator<IntegerPolicyValue> CREATOR =
+            new Creator<IntegerPolicyValue>() {
+                @Override
+                public IntegerPolicyValue createFromParcel(Parcel source) {
+                    return new IntegerPolicyValue(source);
+                }
+
+                @Override
+                public IntegerPolicyValue[] newArray(int size) {
+                    return new IntegerPolicyValue[size];
+                }
+            };
+}
diff --git a/core/java/android/app/admin/IntentFilterPolicyKey.java b/core/java/android/app/admin/IntentFilterPolicyKey.java
new file mode 100644
index 0000000..d7eb101
--- /dev/null
+++ b/core/java/android/app/admin/IntentFilterPolicyKey.java
@@ -0,0 +1,152 @@
+/*
+ * 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 android.app.admin;
+
+import static android.app.admin.PolicyUpdatesReceiver.EXTRA_INTENT_FILTER;
+import static android.app.admin.PolicyUpdatesReceiver.EXTRA_POLICY_BUNDLE_KEY;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.Objects;
+
+/**
+ * Class used to identify a policy that relates to a certain {@link IntentFilter}
+ * (e.g. {@link DevicePolicyManager#addPersistentPreferredActivity}).
+ *
+ * @hide
+ */
+@SystemApi
+public final class IntentFilterPolicyKey extends PolicyKey {
+    private final IntentFilter mFilter;
+
+    /**
+     * @hide
+     */
+    public IntentFilterPolicyKey(@NonNull String identifier, @NonNull IntentFilter filter) {
+        super(identifier);
+        mFilter = Objects.requireNonNull(filter);
+    }
+
+    /**
+     * @hide
+     */
+    public IntentFilterPolicyKey(@NonNull String identifier) {
+        super(identifier);
+        mFilter = null;
+    }
+
+    private IntentFilterPolicyKey(Parcel source) {
+        super(source.readString());
+        mFilter = source.readTypedObject(IntentFilter.CREATOR);
+    }
+
+    /**
+     * Returns the {@link IntentFilter} this policy relates to.
+     */
+    @NonNull
+    public IntentFilter getIntentFilter() {
+        return mFilter;
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public void saveToXml(TypedXmlSerializer serializer) throws IOException {
+        serializer.attribute(/* namespace= */ null, ATTR_POLICY_IDENTIFIER, getIdentifier());
+        mFilter.writeToXml(serializer);
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public IntentFilterPolicyKey readFromXml(TypedXmlPullParser parser)
+            throws XmlPullParserException, IOException {
+        String identifier = parser.getAttributeValue(/* namespace= */ null, ATTR_POLICY_IDENTIFIER);
+        IntentFilter filter = new IntentFilter();
+        filter.readFromXml(parser);
+        return new IntentFilterPolicyKey(identifier, filter);
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public void writeToBundle(Bundle bundle) {
+        super.writeToBundle(bundle);
+        Bundle extraPolicyParams = new Bundle();
+        extraPolicyParams.putParcelable(EXTRA_INTENT_FILTER, mFilter);
+        bundle.putBundle(EXTRA_POLICY_BUNDLE_KEY, extraPolicyParams);
+    }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        IntentFilterPolicyKey other = (IntentFilterPolicyKey) o;
+        return Objects.equals(getIdentifier(), other.getIdentifier())
+                && IntentFilter.filterEquals(mFilter, other.mFilter);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(getIdentifier(), mFilter);
+    }
+
+    @Override
+    public String toString() {
+        return "IntentFilterPolicyKey{mKey= " + getIdentifier() + "; mFilter= " + mFilter + "}";
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString(getIdentifier());
+        mFilter.writeToParcel(dest, flags);
+    }
+
+    @NonNull
+    public static final Parcelable.Creator<IntentFilterPolicyKey> CREATOR =
+            new Parcelable.Creator<IntentFilterPolicyKey>() {
+                @Override
+                public IntentFilterPolicyKey createFromParcel(Parcel source) {
+                    return new IntentFilterPolicyKey(source);
+                }
+
+                @Override
+                public IntentFilterPolicyKey[] newArray(int size) {
+                    return new IntentFilterPolicyKey[size];
+                }
+            };
+}
diff --git a/core/java/android/app/admin/LockTaskPolicy.java b/core/java/android/app/admin/LockTaskPolicy.java
new file mode 100644
index 0000000..28757df
--- /dev/null
+++ b/core/java/android/app/admin/LockTaskPolicy.java
@@ -0,0 +1,164 @@
+/*
+ * 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 android.app.admin;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Class to represent a lock task policy, this is a combination of lock task packages (see
+ * {@link DevicePolicyManager#setLockTaskPackages}) and lock task features (see
+ * {@link DevicePolicyManager#setLockTaskFeatures}.
+ *
+ * @hide
+ */
+@SystemApi
+public final class LockTaskPolicy extends PolicyValue<LockTaskPolicy> {
+    /**
+     * @hide
+     */
+    public static final int DEFAULT_LOCK_TASK_FLAG =
+            DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS;
+
+    private Set<String> mPackages = new HashSet<>();
+    private int mFlags = DEFAULT_LOCK_TASK_FLAG;
+
+    /**
+     * Returns the list of packages allowed to start the lock task mode.
+     */
+    @NonNull
+    public Set<String> getPackages() {
+        return mPackages;
+    }
+
+    /**
+     * Returns which system features are enabled for LockTask mode.
+     */
+    public @DevicePolicyManager.LockTaskFeature int getFlags() {
+        return mFlags;
+    }
+
+    // Overriding to hide
+    /**
+     * @hide
+     */
+    @Override
+    @NonNull
+    public LockTaskPolicy getValue() {
+        return super.getValue();
+    }
+
+    /**
+     * @hide
+     */
+    public LockTaskPolicy(@NonNull Set<String> packages) {
+        Objects.requireNonNull(packages);
+        mPackages.addAll(packages);
+    }
+
+    /**
+     * @hide
+     */
+    public LockTaskPolicy(@NonNull Set<String> packages, int flags) {
+        Objects.requireNonNull(packages);
+        mPackages = new HashSet<>(packages);
+        mFlags = flags;
+        setValue(this);
+    }
+
+    private LockTaskPolicy(Parcel source) {
+        String[] packages = Objects.requireNonNull(source.readStringArray());
+        mPackages = new HashSet<>(Arrays.stream(packages).toList());
+        mFlags = source.readInt();
+    }
+
+    /**
+     * @hide
+     */
+    public LockTaskPolicy(LockTaskPolicy policy) {
+        mPackages = new HashSet<>(policy.mPackages);
+        mFlags = policy.mFlags;
+    }
+
+    /**
+     * @hide
+     */
+    public void setPackages(@NonNull Set<String> packages) {
+        Objects.requireNonNull(packages);
+        mPackages = new HashSet<>(packages);
+    }
+
+    /**
+     * @hide
+     */
+    public void setFlags(int flags) {
+        mFlags = flags;
+    }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        LockTaskPolicy other = (LockTaskPolicy) o;
+        return Objects.equals(mPackages, other.mPackages)
+                && mFlags == other.mFlags;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mPackages, mFlags);
+    }
+
+    @Override
+    public String toString() {
+        return "LockTaskPolicy {mPackages= " + String.join(", ", mPackages) + "; mFlags= "
+                + mFlags + " }";
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeArray(mPackages.toArray(new String[0]));
+        dest.writeInt(mFlags);
+    }
+
+    @NonNull
+    public static final Parcelable.Creator<LockTaskPolicy> CREATOR =
+            new Parcelable.Creator<LockTaskPolicy>() {
+                @Override
+                public LockTaskPolicy createFromParcel(Parcel source) {
+                    return new LockTaskPolicy(source);
+                }
+
+                @Override
+                public LockTaskPolicy[] newArray(int size) {
+                    return new LockTaskPolicy[size];
+                }
+            };
+}
diff --git a/core/java/android/app/admin/MostRecent.java b/core/java/android/app/admin/MostRecent.java
new file mode 100644
index 0000000..ac1657189
--- /dev/null
+++ b/core/java/android/app/admin/MostRecent.java
@@ -0,0 +1,61 @@
+/*
+ * 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.app.admin;
+
+
+import android.annotation.NonNull;
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Class to identify a most-recent-setter wins resolution mechanism that is used to resolve the
+ * enforced policy when being set by multiple admins (see
+ * {@link PolicyState#getResolutionMechanism()}).
+ *
+ * @hide
+ */
+@TestApi
+public final class MostRecent<V> extends ResolutionMechanism<V> {
+
+    @Override
+    public String toString() {
+        return "MostRecent {}";
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {}
+
+    @NonNull
+    public static final Parcelable.Creator<MostRecent> CREATOR =
+            new Parcelable.Creator<MostRecent>() {
+                @Override
+                public MostRecent createFromParcel(Parcel source) {
+                    return new MostRecent();
+                }
+
+                @Override
+                public MostRecent[] newArray(int size) {
+                    return new MostRecent[size];
+                }
+            };
+}
diff --git a/core/java/android/app/admin/MostRestrictive.java b/core/java/android/app/admin/MostRestrictive.java
new file mode 100644
index 0000000..adb4744
--- /dev/null
+++ b/core/java/android/app/admin/MostRestrictive.java
@@ -0,0 +1,109 @@
+/*
+ * 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.app.admin;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Class to identify a most restrictive resolution mechanism that is used to resolve the enforced
+ * policy when being set by multiple admins (see {@link PolicyState#getResolutionMechanism()}).
+ *
+ * @hide
+ */
+@TestApi
+public final class MostRestrictive<V> extends ResolutionMechanism<V> {
+
+    private final List<PolicyValue<V>> mMostToLeastRestrictive;
+
+    /**
+     * @hide
+     */
+    public MostRestrictive(@NonNull List<PolicyValue<V>> mostToLeastRestrictive) {
+        mMostToLeastRestrictive = new ArrayList<>(mostToLeastRestrictive);
+    }
+
+    /**
+     * Returns an ordered list of most to least restrictive values for a certain policy.
+     */
+    List<V> getMostToLeastRestrictiveValues() {
+        return mMostToLeastRestrictive.stream().map(PolicyValue::getValue).toList();
+    }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        MostRestrictive other = (MostRestrictive) o;
+        return Objects.equals(mMostToLeastRestrictive, other.mMostToLeastRestrictive);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mMostToLeastRestrictive);
+    }
+
+    /**
+     * @hide
+     */
+    public MostRestrictive(Parcel source) {
+        mMostToLeastRestrictive = new ArrayList<>();
+        int size = source.readInt();
+        for (int i = 0; i < size; i++) {
+            mMostToLeastRestrictive.add(source.readParcelable(PolicyValue.class.getClassLoader()));
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "MostRestrictive { mMostToLeastRestrictive= " + mMostToLeastRestrictive + " }";
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mMostToLeastRestrictive.size());
+        for (PolicyValue<V> entry : mMostToLeastRestrictive) {
+            dest.writeParcelable(entry, flags);
+        }
+    }
+
+    @NonNull
+    public static final Parcelable.Creator<MostRestrictive<?>> CREATOR =
+            new Parcelable.Creator<MostRestrictive<?>>() {
+                @Override
+                public MostRestrictive<?> createFromParcel(Parcel source) {
+                    return new MostRestrictive<>(source);
+                }
+
+                @Override
+                public MostRestrictive<?>[] newArray(int size) {
+                    return new MostRestrictive[size];
+                }
+            };
+}
diff --git a/core/java/android/app/admin/NoArgsPolicyKey.java b/core/java/android/app/admin/NoArgsPolicyKey.java
new file mode 100644
index 0000000..57b67d5
--- /dev/null
+++ b/core/java/android/app/admin/NoArgsPolicyKey.java
@@ -0,0 +1,72 @@
+/*
+ * 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 android.app.admin;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Default implementation for {@link PolicyKey} used to identify a policy that doesn't require any
+ * additional arguments to be represented.
+ *
+ * @hide
+ */
+@SystemApi
+public final class NoArgsPolicyKey extends PolicyKey {
+
+    /**
+     * @hide
+     */
+    public NoArgsPolicyKey(@NonNull String identifier) {
+        super(identifier);
+    }
+
+    private NoArgsPolicyKey(Parcel source) {
+        this(source.readString());
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString(getIdentifier());
+    }
+
+    @NonNull
+    public static final Parcelable.Creator<NoArgsPolicyKey> CREATOR =
+            new Parcelable.Creator<NoArgsPolicyKey>() {
+                @Override
+                public NoArgsPolicyKey createFromParcel(Parcel source) {
+                    return new NoArgsPolicyKey(source);
+                }
+
+                @Override
+                public NoArgsPolicyKey[] newArray(int size) {
+                    return new NoArgsPolicyKey[size];
+                }
+            };
+
+    @Override
+    public String toString() {
+        return "DefaultPolicyKey " + getIdentifier();
+    }
+}
diff --git a/core/java/android/app/admin/PackagePermissionPolicyKey.java b/core/java/android/app/admin/PackagePermissionPolicyKey.java
new file mode 100644
index 0000000..4aa2e38
--- /dev/null
+++ b/core/java/android/app/admin/PackagePermissionPolicyKey.java
@@ -0,0 +1,175 @@
+/*
+ * 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 android.app.admin;
+
+import static android.app.admin.PolicyUpdatesReceiver.EXTRA_PACKAGE_NAME;
+import static android.app.admin.PolicyUpdatesReceiver.EXTRA_PERMISSION_NAME;
+import static android.app.admin.PolicyUpdatesReceiver.EXTRA_POLICY_BUNDLE_KEY;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.Objects;
+
+/**
+ * Class used to identify a policy that relates to a certain package and permission
+ * (e.g. {@link DevicePolicyManager#setPermissionGrantState}).
+ *
+ * @hide
+ */
+@SystemApi
+public final class PackagePermissionPolicyKey extends PolicyKey {
+    private static final String ATTR_PACKAGE_NAME = "package-name";
+    private static final String ATTR_PERMISSION_NAME = "permission-name";
+
+    private final String mPackageName;
+    private final String mPermissionName;
+
+    /**
+     * @hide
+     */
+    public PackagePermissionPolicyKey(@NonNull String identifier, @NonNull String packageName,
+            @NonNull String permissionName) {
+        super(identifier);
+        mPackageName = Objects.requireNonNull((packageName));
+        mPermissionName = Objects.requireNonNull((permissionName));
+    }
+
+    /**
+     * @hide
+     */
+    public PackagePermissionPolicyKey(@NonNull String identifier) {
+        super(identifier);
+        mPackageName = null;
+        mPermissionName = null;
+    }
+
+    private PackagePermissionPolicyKey(Parcel source) {
+        super(source.readString());
+        mPackageName = source.readString();
+        mPermissionName = source.readString();
+    }
+
+    /**
+     * Returns the package name this policy relates to.
+     */
+    @NonNull
+    public String getPackageName() {
+        return mPackageName;
+    }
+
+    /**
+     * Returns the permission name this policy relates to.
+     */
+    @NonNull
+    public String getPermissionName() {
+        return mPermissionName;
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public void saveToXml(TypedXmlSerializer serializer) throws IOException {
+        serializer.attribute(/* namespace= */ null, ATTR_POLICY_IDENTIFIER, getIdentifier());
+        serializer.attribute(/* namespace= */ null, ATTR_PACKAGE_NAME, mPackageName);
+        serializer.attribute(/* namespace= */ null, ATTR_PERMISSION_NAME, mPermissionName);
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public PackagePermissionPolicyKey readFromXml(TypedXmlPullParser parser)
+            throws XmlPullParserException, IOException {
+        String identifier = parser.getAttributeValue(
+                /* namespace= */ null, ATTR_POLICY_IDENTIFIER);
+        String packageName = parser.getAttributeValue(/* namespace= */ null, ATTR_PACKAGE_NAME);
+        String permissionName = parser.getAttributeValue(
+                /* namespace= */ null, ATTR_PERMISSION_NAME);
+        return new PackagePermissionPolicyKey(identifier, packageName, permissionName);
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public void writeToBundle(Bundle bundle) {
+        super.writeToBundle(bundle);
+        Bundle extraPolicyParams = new Bundle();
+        extraPolicyParams.putString(EXTRA_PACKAGE_NAME, mPackageName);
+        extraPolicyParams.putString(EXTRA_PERMISSION_NAME, mPermissionName);
+        bundle.putBundle(EXTRA_POLICY_BUNDLE_KEY, extraPolicyParams);
+    }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        PackagePermissionPolicyKey other = (PackagePermissionPolicyKey) o;
+        return Objects.equals(getIdentifier(), other.getIdentifier())
+                && Objects.equals(mPackageName, other.mPackageName)
+                && Objects.equals(mPermissionName, other.mPermissionName);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(getIdentifier(), mPackageName, mPermissionName);
+    }
+
+    @Override
+    public String toString() {
+        return "PackagePermissionPolicyKey{mIdentifier= " + getIdentifier() + "; mPackageName= "
+                + mPackageName + "; mPermissionName= " + mPermissionName + "}";
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString(getIdentifier());
+        dest.writeString(mPackageName);
+        dest.writeString(mPermissionName);
+    }
+
+    @NonNull
+    public static final Parcelable.Creator<PackagePermissionPolicyKey> CREATOR =
+            new Parcelable.Creator<PackagePermissionPolicyKey>() {
+                @Override
+                public PackagePermissionPolicyKey createFromParcel(Parcel source) {
+                    return new PackagePermissionPolicyKey(source);
+                }
+
+                @Override
+                public PackagePermissionPolicyKey[] newArray(int size) {
+                    return new PackagePermissionPolicyKey[size];
+                }
+            };
+}
diff --git a/core/java/android/app/admin/PackagePolicyKey.java b/core/java/android/app/admin/PackagePolicyKey.java
new file mode 100644
index 0000000..3469970
--- /dev/null
+++ b/core/java/android/app/admin/PackagePolicyKey.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.admin;
+
+import static android.app.admin.PolicyUpdatesReceiver.EXTRA_PACKAGE_NAME;
+import static android.app.admin.PolicyUpdatesReceiver.EXTRA_POLICY_BUNDLE_KEY;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.Objects;
+
+/**
+ * Class used to identify a policy that relates to a certain package
+ * (e.g. {@link DevicePolicyManager#setUninstallBlocked}).
+ *
+ * @hide
+ */
+@SystemApi
+public final class PackagePolicyKey extends PolicyKey {
+    private static final String ATTR_PACKAGE_NAME = "package-name";
+
+    private final String mPackageName;
+
+    /**
+     * @hide
+     */
+    public PackagePolicyKey(@NonNull String key, @NonNull String packageName) {
+        super(key);
+        mPackageName = Objects.requireNonNull((packageName));
+    }
+
+    private PackagePolicyKey(Parcel source) {
+        super(source.readString());
+        mPackageName = source.readString();
+    }
+
+    /**
+     * @hide
+     */
+    public PackagePolicyKey(String key) {
+        super(key);
+        mPackageName = null;
+    }
+
+    /**
+     * Returns the package name this policy relates to.
+     */
+    @NonNull
+    public String getPackageName() {
+        return mPackageName;
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public void saveToXml(TypedXmlSerializer serializer) throws IOException {
+        serializer.attribute(/* namespace= */ null, ATTR_POLICY_IDENTIFIER, getIdentifier());
+        serializer.attribute(/* namespace= */ null, ATTR_PACKAGE_NAME, mPackageName);
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public PackagePolicyKey readFromXml(TypedXmlPullParser parser)
+            throws XmlPullParserException, IOException {
+        String policyKey = parser.getAttributeValue(/* namespace= */ null,
+                ATTR_POLICY_IDENTIFIER);
+        String packageName = parser.getAttributeValue(/* namespace= */ null, ATTR_PACKAGE_NAME);
+        return new PackagePolicyKey(policyKey, packageName);
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public void writeToBundle(Bundle bundle) {
+        super.writeToBundle(bundle);
+        Bundle extraPolicyParams = new Bundle();
+        extraPolicyParams.putString(EXTRA_PACKAGE_NAME, mPackageName);
+        bundle.putBundle(EXTRA_POLICY_BUNDLE_KEY, extraPolicyParams);
+    }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        PackagePolicyKey other = (PackagePolicyKey) o;
+        return Objects.equals(getIdentifier(), other.getIdentifier())
+                && Objects.equals(mPackageName, other.mPackageName);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(getIdentifier(), mPackageName);
+    }
+
+    @Override
+    public String toString() {
+        return "PackagePolicyKey{mPolicyKey= " + getIdentifier()
+                + "; mPackageName= " + mPackageName + "}";
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString(getIdentifier());
+        dest.writeString(mPackageName);
+    }
+
+    @NonNull
+    public static final Parcelable.Creator<PackagePolicyKey> CREATOR =
+            new Parcelable.Creator<PackagePolicyKey>() {
+                @Override
+                public PackagePolicyKey createFromParcel(Parcel source) {
+                    return new PackagePolicyKey(source);
+                }
+
+                @Override
+                public PackagePolicyKey[] newArray(int size) {
+                    return new PackagePolicyKey[size];
+                }
+            };
+}
diff --git a/core/java/android/app/admin/PolicyKey.java b/core/java/android/app/admin/PolicyKey.java
new file mode 100644
index 0000000..a35f634
--- /dev/null
+++ b/core/java/android/app/admin/PolicyKey.java
@@ -0,0 +1,123 @@
+/*
+ * 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 android.app.admin;
+
+import static android.app.admin.PolicyUpdatesReceiver.EXTRA_POLICY_KEY;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.os.Bundle;
+import android.os.Parcelable;
+
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.Objects;
+
+/**
+ * Abstract class used to identify a policy returned from
+ * {@link DevicePolicyManager#getDevicePolicyState()}.
+ *
+ * @hide
+ */
+// This is ok as the constructor is hidden and all subclasses have implemented
+// Parcelable.
+@SuppressLint({"ParcelNotFinal", "ParcelCreator"})
+@SystemApi
+public abstract class PolicyKey implements Parcelable {
+    /**
+     * @hide
+     */
+    static final String ATTR_POLICY_IDENTIFIER = "policy-identifier";
+
+    private final String mIdentifier;
+
+    /**
+     * @hide
+     */
+    protected PolicyKey(@NonNull String identifier) {
+        mIdentifier = Objects.requireNonNull(identifier);
+    }
+
+    /**
+     * Returns the string identifier for this policy.
+     */
+    @NonNull
+    public String getIdentifier() {
+        return mIdentifier;
+    }
+
+    /**
+     * @hide
+     */
+    public boolean hasSameIdentifierAs(PolicyKey other) {
+        if (other == null) {
+            return false;
+        }
+        return mIdentifier.equals(other.mIdentifier);
+    }
+
+    /**
+     * @hide
+     */
+    public static PolicyKey readGenericPolicyKeyFromXml(TypedXmlPullParser parser) {
+        String identifier = parser.getAttributeValue(
+                /* namespace= */ null, ATTR_POLICY_IDENTIFIER);
+        return new NoArgsPolicyKey(identifier);
+    }
+
+    /**
+     * @hide
+     */
+    public void saveToXml(TypedXmlSerializer serializer) throws IOException {
+        serializer.attribute(/* namespace= */ null, ATTR_POLICY_IDENTIFIER, mIdentifier);
+    }
+
+    /**
+     * @hide
+     */
+    public PolicyKey readFromXml(TypedXmlPullParser parser)
+            throws XmlPullParserException, IOException {
+        // No need to read anything
+        return this;
+    }
+
+    /**
+     * @hide
+     */
+    public void writeToBundle(Bundle bundle) {
+        bundle.putString(EXTRA_POLICY_KEY, mIdentifier);
+    }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        PolicyKey other = (PolicyKey) o;
+        return Objects.equals(mIdentifier, other.mIdentifier);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mIdentifier);
+    }
+}
diff --git a/core/java/android/app/admin/PolicyState.java b/core/java/android/app/admin/PolicyState.java
new file mode 100644
index 0000000..da71bb1
--- /dev/null
+++ b/core/java/android/app/admin/PolicyState.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.admin;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.LinkedHashMap;
+import java.util.Objects;
+
+/**
+ * Class containing the state of a certain policy (e.g. all values set by different admins,
+ * current resolved policy, etc).
+ *
+ * <p>Note that the value returned from {@link #getCurrentResolvedPolicy()} might not match any
+ * of the values in {@link #getPoliciesSetByAdmins()} as some policies might be affected by a
+ * conflicting global policy set on the device (retrieved using
+ * {@link DevicePolicyState#getPoliciesForUser} with {@link android.os.UserHandle#ALL}.
+ *
+ * @hide
+ */
+@SystemApi
+public final class PolicyState<V> implements Parcelable {
+    private final LinkedHashMap<EnforcingAdmin, PolicyValue<V>> mPoliciesSetByAdmins =
+            new LinkedHashMap<>();
+    private PolicyValue<V> mCurrentResolvedPolicy;
+    private ResolutionMechanism<V> mResolutionMechanism;
+
+    /**
+     * @hide
+     */
+    public PolicyState(
+            @NonNull LinkedHashMap<EnforcingAdmin, PolicyValue<V>> policiesSetByAdmins,
+            PolicyValue<V> currentEnforcedPolicy,
+            @NonNull ResolutionMechanism<V> resolutionMechanism) {
+        Objects.requireNonNull(policiesSetByAdmins);
+        Objects.requireNonNull(resolutionMechanism);
+
+        mPoliciesSetByAdmins.putAll(policiesSetByAdmins);
+        mCurrentResolvedPolicy = currentEnforcedPolicy;
+        mResolutionMechanism = resolutionMechanism;
+    }
+
+    private PolicyState(Parcel source) {
+        int size = source.readInt();
+        for (int i = 0; i < size; i++) {
+            EnforcingAdmin admin = source.readParcelable(EnforcingAdmin.class.getClassLoader());
+            PolicyValue<V> policyValue = source.readParcelable(PolicyValue.class.getClassLoader());
+            mPoliciesSetByAdmins.put(admin, policyValue);
+        }
+        mCurrentResolvedPolicy = source.readParcelable((PolicyValue.class.getClassLoader()));
+        mResolutionMechanism = source.readParcelable(ResolutionMechanism.class.getClassLoader());
+    }
+
+    /**
+     * Returns all values set by admins for this policy
+     */
+    @NonNull
+    public LinkedHashMap<EnforcingAdmin, V> getPoliciesSetByAdmins() {
+        LinkedHashMap<EnforcingAdmin, V> policies = new LinkedHashMap<>();
+        for (EnforcingAdmin admin : mPoliciesSetByAdmins.keySet()) {
+            policies.put(admin, mPoliciesSetByAdmins.get(admin).getValue());
+        }
+        return policies;
+    }
+
+    /**
+     * Returns the current resolved policy value.
+     */
+    @Nullable
+    public V getCurrentResolvedPolicy() {
+        return mCurrentResolvedPolicy.getValue();
+    }
+
+    /**
+     * Returns the resolution mechanism used to resolve the enforced policy when the policy has
+     * been set by multiple enforcing admins {@link EnforcingAdmin}.
+     *
+     * @hide
+     */
+    @TestApi
+    @NonNull
+    public ResolutionMechanism<V> getResolutionMechanism() {
+        return mResolutionMechanism;
+    }
+
+    @Override
+    public String toString() {
+        return "PolicyState { mPoliciesSetByAdmins= "
+                + mPoliciesSetByAdmins + ", mCurrentResolvedPolicy= " + mCurrentResolvedPolicy
+                + ", mResolutionMechanism= " + mResolutionMechanism + " }";
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mPoliciesSetByAdmins.size());
+        for (EnforcingAdmin admin : mPoliciesSetByAdmins.keySet()) {
+            dest.writeParcelable(admin, flags);
+            dest.writeParcelable(mPoliciesSetByAdmins.get(admin), flags);
+        }
+        dest.writeParcelable(mCurrentResolvedPolicy, flags);
+        dest.writeParcelable(mResolutionMechanism, flags);
+    }
+
+    @NonNull
+    public static final Parcelable.Creator<PolicyState<?>> CREATOR =
+            new Parcelable.Creator<PolicyState<?>>() {
+                @Override
+                public PolicyState<?> createFromParcel(Parcel source) {
+                    return new PolicyState<>(source);
+                }
+
+                @Override
+                public PolicyState<?>[] newArray(int size) {
+                    return new PolicyState[size];
+                }
+            };
+}
diff --git a/core/java/android/app/admin/PolicyUpdateResult.java b/core/java/android/app/admin/PolicyUpdateResult.java
index 9e13e00..a6d0ebf 100644
--- a/core/java/android/app/admin/PolicyUpdateResult.java
+++ b/core/java/android/app/admin/PolicyUpdateResult.java
@@ -48,6 +48,14 @@
     public static final int RESULT_FAILURE_CONFLICTING_ADMIN_POLICY = 1;
 
     /**
+     * Result code to indicate that the policy set by the admin has been successfully cleared,
+     * admins will no longer receive policy updates for this policy after this point.
+     *
+     * <p>Note that the policy can still be enforced by some other admin.
+     */
+    public static final int RESULT_POLICY_CLEARED = 2;
+
+    /**
      * Reason codes for {@link #getResultCode()}.
      *
      * @hide
@@ -56,7 +64,8 @@
     @IntDef(flag = true, prefix = { "RESULT_" }, value = {
             RESULT_FAILURE_UNKNOWN,
             RESULT_SUCCESS,
-            RESULT_FAILURE_CONFLICTING_ADMIN_POLICY
+            RESULT_FAILURE_CONFLICTING_ADMIN_POLICY,
+            RESULT_POLICY_CLEARED
     })
     public @interface ResultCode {}
 
diff --git a/core/java/android/app/admin/PolicyValue.java b/core/java/android/app/admin/PolicyValue.java
new file mode 100644
index 0000000..b0a884b
--- /dev/null
+++ b/core/java/android/app/admin/PolicyValue.java
@@ -0,0 +1,46 @@
+/*
+ * 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 android.app.admin;
+
+import android.annotation.NonNull;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Wrapper class to ensure that all policy values stored in the policy engine are parcelable.
+ *
+ * @hide
+ */
+public abstract class PolicyValue<V> implements Parcelable {
+    private V mValue;
+
+    public PolicyValue(V value) {
+        mValue = Objects.requireNonNull(value);
+    }
+
+    PolicyValue() {}
+
+    @NonNull
+    public V getValue() {
+        return mValue;
+    }
+
+    void setValue(V value) {
+        mValue = value;
+    }
+}
diff --git a/core/java/android/app/admin/ResolutionMechanism.java b/core/java/android/app/admin/ResolutionMechanism.java
new file mode 100644
index 0000000..a7c6842
--- /dev/null
+++ b/core/java/android/app/admin/ResolutionMechanism.java
@@ -0,0 +1,38 @@
+/*
+ * 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.app.admin;
+
+import android.annotation.SuppressLint;
+import android.annotation.TestApi;
+import android.os.Parcelable;
+
+/**
+ * Abstract class to identify a resolution mechanism that is used to resolve the enforced
+ * policy when being set by multiple admins (see {@link PolicyState#getResolutionMechanism()}).
+ *
+ * @hide
+ */
+// This is ok as the constructor is hidden and all subclasses have implemented
+// Parcelable.
+@SuppressLint({"ParcelNotFinal", "ParcelCreator"})
+@TestApi
+public abstract class ResolutionMechanism<V> implements Parcelable {
+    /**
+     * @hide
+     */
+    ResolutionMechanism(){}
+}
diff --git a/core/java/android/app/admin/RoleAuthority.java b/core/java/android/app/admin/RoleAuthority.java
new file mode 100644
index 0000000..7fdd118
--- /dev/null
+++ b/core/java/android/app/admin/RoleAuthority.java
@@ -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 android.app.admin;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Class used to identify the authority of the {@link EnforcingAdmin} based on a certain role/roles
+ * it holds.
+ *
+ * @hide
+ */
+@SystemApi
+public final class RoleAuthority extends Authority {
+    private final Set<String> mRoles;
+
+    /**
+     * @hide
+     */
+    public RoleAuthority(@NonNull Set<String> roles) {
+        mRoles = new HashSet<>(Objects.requireNonNull(roles));
+    }
+
+    private RoleAuthority(Parcel source) {
+        String[] roles = source.readStringArray();
+        mRoles = roles == null ? new HashSet<>() : new HashSet<>(Arrays.stream(roles).toList());
+    }
+
+    /**
+     * Returns the list of roles held by the associated admin.
+     */
+    @NonNull
+    public Set<String> getRoles() {
+        return mRoles;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeArray(mRoles.toArray());
+    }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        RoleAuthority other = (RoleAuthority) o;
+        return Objects.equals(mRoles, other.mRoles);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mRoles);
+    }
+
+    @Override
+    public String toString() {
+        return "RoleAuthority { mRoles= " + mRoles + " }";
+    }
+
+    @NonNull
+    public static final Parcelable.Creator<RoleAuthority> CREATOR =
+            new Parcelable.Creator<RoleAuthority>() {
+                @Override
+                public RoleAuthority createFromParcel(Parcel source) {
+                    return new RoleAuthority(source);
+                }
+
+                @Override
+                public RoleAuthority[] newArray(int size) {
+                    return new RoleAuthority[size];
+                }
+            };
+}
diff --git a/core/java/android/app/admin/StringPolicyValue.java b/core/java/android/app/admin/StringPolicyValue.java
new file mode 100644
index 0000000..14b6dab
--- /dev/null
+++ b/core/java/android/app/admin/StringPolicyValue.java
@@ -0,0 +1,78 @@
+/*
+ * 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 android.app.admin;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+
+import java.util.Objects;
+
+/**
+ * @hide
+ */
+public final class StringPolicyValue extends PolicyValue<String> {
+
+    public StringPolicyValue(@NonNull String value) {
+        super(value);
+    }
+
+    private StringPolicyValue(Parcel source) {
+        super(source.readString());
+    }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        StringPolicyValue other = (StringPolicyValue) o;
+        return Objects.equals(getValue(), other.getValue());
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(getValue());
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public String toString() {
+        return "StringPolicyValue { " + getValue() + " }";
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString(getValue());
+    }
+
+    @NonNull
+    public static final Creator<StringPolicyValue> CREATOR = new Creator<StringPolicyValue>() {
+                @Override
+                public StringPolicyValue createFromParcel(Parcel source) {
+                    return new StringPolicyValue(source);
+                }
+
+                @Override
+                public StringPolicyValue[] newArray(int size) {
+                    return new StringPolicyValue[size];
+                }
+            };
+}
diff --git a/core/java/android/app/admin/StringSetPolicyValue.java b/core/java/android/app/admin/StringSetPolicyValue.java
new file mode 100644
index 0000000..cbfc604
--- /dev/null
+++ b/core/java/android/app/admin/StringSetPolicyValue.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.admin;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * @hide
+ */
+public final class StringSetPolicyValue extends PolicyValue<Set<String>> {
+
+    public StringSetPolicyValue(@NonNull Set<String> value) {
+        super(value);
+    }
+
+    public StringSetPolicyValue(Parcel source) {
+        this(readValues(source));
+    }
+
+    private static Set<String> readValues(Parcel source) {
+        Set<String> values = new HashSet<>();
+        int size = source.readInt();
+        for (int i = 0; i < size; i++) {
+            values.add(source.readString());
+        }
+        return values;
+    }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        StringSetPolicyValue other = (StringSetPolicyValue) o;
+        return Objects.equals(getValue(), other.getValue());
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(getValue());
+    }
+
+    @Override
+    public String toString() {
+        return "StringSetPolicyValue { " + getValue() + " }";
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(getValue().size());
+        for (String entry : getValue()) {
+            dest.writeString(entry);
+        }
+    }
+
+    @NonNull
+    public static final Creator<StringSetPolicyValue> CREATOR =
+            new Creator<StringSetPolicyValue>() {
+                @Override
+                public StringSetPolicyValue createFromParcel(Parcel source) {
+                    return new StringSetPolicyValue(source);
+                }
+
+                @Override
+                public StringSetPolicyValue[] newArray(int size) {
+                    return new StringSetPolicyValue[size];
+                }
+            };
+}
diff --git a/core/java/android/app/admin/StringSetUnion.java b/core/java/android/app/admin/StringSetUnion.java
new file mode 100644
index 0000000..730e6a2
--- /dev/null
+++ b/core/java/android/app/admin/StringSetUnion.java
@@ -0,0 +1,62 @@
+/*
+ * 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.app.admin;
+
+import android.annotation.NonNull;
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Set;
+
+/**
+ * Class to identify a union resolution mechanism for {@code Set<String>} policies, it's used to
+ * resolve the enforced policy when being set by multiple admins (see
+ * {@link PolicyState#getResolutionMechanism()}).
+ *
+ * @hide
+ */
+@TestApi
+public final class StringSetUnion extends ResolutionMechanism<Set<String>> {
+
+    @Override
+    public String toString() {
+        return "StringSetUnion {}";
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {}
+
+    @NonNull
+    public static final Parcelable.Creator<StringSetUnion> CREATOR =
+            new Parcelable.Creator<StringSetUnion>() {
+                @Override
+                public StringSetUnion createFromParcel(Parcel source) {
+                    return new StringSetUnion();
+                }
+
+                @Override
+                public StringSetUnion[] newArray(int size) {
+                    return new StringSetUnion[size];
+                }
+            };
+}
diff --git a/core/java/android/app/admin/TopPriority.java b/core/java/android/app/admin/TopPriority.java
new file mode 100644
index 0000000..e712274
--- /dev/null
+++ b/core/java/android/app/admin/TopPriority.java
@@ -0,0 +1,89 @@
+/*
+ * 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.app.admin;
+
+import android.annotation.NonNull;
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Class to identify a top priority resolution mechanism that is used to resolve the enforced
+ * policy when being set by multiple admins (see {@link PolicyState#getResolutionMechanism()}).
+ *
+ * <p>Priorities are defined based on the calling admin's {@link Authority}.
+ *
+ * @hide
+ */
+@TestApi
+public final class TopPriority<V> extends ResolutionMechanism<V> {
+
+    private final List<String> mHighestToLowestPriorityAuthorities;
+
+    /**
+     * @hide
+     */
+    public TopPriority(@NonNull List<String> highestToLowestPriorityAuthorities) {
+        mHighestToLowestPriorityAuthorities = Objects.requireNonNull(
+                highestToLowestPriorityAuthorities);
+    }
+
+    /**
+     * Returns an ordered list of authorities from highest priority to lowest priority for a
+     * certain policy.
+     */
+    @NonNull
+    List<String> getHighestToLowestPriorityAuthorities() {
+        return mHighestToLowestPriorityAuthorities;
+    }
+
+    @Override
+    public String toString() {
+        return "TopPriority { mHighestToLowestPriorityAuthorities= "
+                + mHighestToLowestPriorityAuthorities + " }";
+    }
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeStringArray(mHighestToLowestPriorityAuthorities.toArray(new String[0]));
+    }
+
+    @NonNull
+    public static final Parcelable.Creator<TopPriority<?>> CREATOR =
+            new Parcelable.Creator<TopPriority<?>>() {
+                @Override
+                public TopPriority<?> createFromParcel(Parcel source) {
+                    String[] highestToLowestPriorityAuthorities = source.readStringArray();
+                    return new TopPriority<>(
+                            Arrays.stream(highestToLowestPriorityAuthorities).toList());
+                }
+
+                @Override
+                public TopPriority<?>[] newArray(int size) {
+                    return new TopPriority[size];
+                }
+            };
+
+}
diff --git a/core/java/android/app/admin/UnknownAuthority.java b/core/java/android/app/admin/UnknownAuthority.java
new file mode 100644
index 0000000..4492b96
--- /dev/null
+++ b/core/java/android/app/admin/UnknownAuthority.java
@@ -0,0 +1,77 @@
+/*
+ * 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 android.app.admin;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+
+/**
+ * Class used to identify a default value for the authority of the {@link EnforcingAdmin} setting
+ * a policy, meaning it is not one of the other known subclasses of {@link Authority}, this would be
+ * the case for example for a system component setting the policy.
+ *
+ * @hide
+ */
+@SystemApi
+public final class UnknownAuthority extends Authority {
+
+    /**
+     * @hide
+     */
+    public static final UnknownAuthority UNKNOWN_AUTHORITY = new UnknownAuthority();
+
+    private UnknownAuthority() {}
+
+    @Override
+    public String toString() {
+        return "DefaultAuthority {}";
+    }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        return super.equals(o);
+    }
+
+    @Override
+    public int hashCode() {
+        return super.hashCode();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {}
+
+    @NonNull
+    public static final Creator<UnknownAuthority> CREATOR =
+            new Creator<UnknownAuthority>() {
+                @Override
+                public UnknownAuthority createFromParcel(Parcel source) {
+                    return new UnknownAuthority();
+                }
+
+                @Override
+                public UnknownAuthority[] newArray(int size) {
+                    return new UnknownAuthority[size];
+                }
+            };
+}
diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java
index 611ad88..d4d323e 100644
--- a/core/java/android/content/IntentFilter.java
+++ b/core/java/android/content/IntentFilter.java
@@ -3039,4 +3039,81 @@
         ArrayList<String> list = getHostsList();
         return list.toArray(new String[list.size()]);
     }
+
+    /**
+     * @hide
+     */
+    public static boolean filterEquals(IntentFilter f1, IntentFilter f2) {
+        int s1 = f1.countActions();
+        int s2 = f2.countActions();
+        if (s1 != s2) {
+            return false;
+        }
+        for (int i=0; i<s1; i++) {
+            if (!f2.hasAction(f1.getAction(i))) {
+                return false;
+            }
+        }
+        s1 = f1.countCategories();
+        s2 = f2.countCategories();
+        if (s1 != s2) {
+            return false;
+        }
+        for (int i=0; i<s1; i++) {
+            if (!f2.hasCategory(f1.getCategory(i))) {
+                return false;
+            }
+        }
+        s1 = f1.countDataTypes();
+        s2 = f2.countDataTypes();
+        if (s1 != s2) {
+            return false;
+        }
+        for (int i=0; i<s1; i++) {
+            if (!f2.hasExactDataType(f1.getDataType(i))) {
+                return false;
+            }
+        }
+        s1 = f1.countDataSchemes();
+        s2 = f2.countDataSchemes();
+        if (s1 != s2) {
+            return false;
+        }
+        for (int i=0; i<s1; i++) {
+            if (!f2.hasDataScheme(f1.getDataScheme(i))) {
+                return false;
+            }
+        }
+        s1 = f1.countDataAuthorities();
+        s2 = f2.countDataAuthorities();
+        if (s1 != s2) {
+            return false;
+        }
+        for (int i=0; i<s1; i++) {
+            if (!f2.hasDataAuthority(f1.getDataAuthority(i))) {
+                return false;
+            }
+        }
+        s1 = f1.countDataPaths();
+        s2 = f2.countDataPaths();
+        if (s1 != s2) {
+            return false;
+        }
+        for (int i=0; i<s1; i++) {
+            if (!f2.hasDataPath(f1.getDataPath(i))) {
+                return false;
+            }
+        }
+        s1 = f1.countDataSchemeSpecificParts();
+        s2 = f2.countDataSchemeSpecificParts();
+        if (s1 != s2) {
+            return false;
+        }
+        for (int i=0; i<s1; i++) {
+            if (!f2.hasDataSchemeSpecificPart(f1.getDataSchemeSpecificPart(i))) {
+                return false;
+            }
+        }
+        return true;
+    }
 }
diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java
index e38cb65..6386f75 100644
--- a/core/java/android/content/pm/UserInfo.java
+++ b/core/java/android/content/pm/UserInfo.java
@@ -22,6 +22,7 @@
 import android.annotation.TestApi;
 import android.annotation.UserIdInt;
 import android.compat.annotation.UnsupportedAppUsage;
+import android.content.res.Resources;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.UserHandle;
@@ -418,16 +419,25 @@
             // Don't support switching to pre-created users until they become "real" users.
             return false;
         }
-        return !isProfile();
+        return isFull() || canSwitchToHeadlessSystemUser();
+    }
+
+    /**
+     * @return true if user is of type {@link UserManager#USER_TYPE_SYSTEM_HEADLESS} and
+     * {@link com.android.internal.R.bool.config_canSwitchToHeadlessSystemUser} is true.
+     */
+    private boolean canSwitchToHeadlessSystemUser() {
+        return UserManager.USER_TYPE_SYSTEM_HEADLESS.equals(userType) && Resources.getSystem()
+                .getBoolean(com.android.internal.R.bool.config_canSwitchToHeadlessSystemUser);
     }
 
     /**
      * @return true if this user can be switched to by end user through UI.
+     * @deprecated Use {@link UserInfo#supportsSwitchTo} instead.
      */
+    @Deprecated
     public boolean supportsSwitchToByUser() {
-        // Hide the system user when it does not represent a human user.
-        boolean hideSystemUser = UserManager.isHeadlessSystemUserMode();
-        return (!hideSystemUser || id != UserHandle.USER_SYSTEM) && supportsSwitchTo();
+        return supportsSwitchTo();
     }
 
     // TODO(b/142482943): Make this logic more specific and customizable. (canHaveProfile(userType))
@@ -436,11 +446,7 @@
         if (isProfile() || isGuest() || isRestricted()) {
             return false;
         }
-        if (UserManager.isHeadlessSystemUserMode()) {
-            return id != UserHandle.USER_SYSTEM;
-        } else {
-            return id == UserHandle.USER_SYSTEM;
-        }
+        return isMain();
     }
 
     // TODO(b/142482943): Get rid of this (after removing it from all tests) if feasible.
diff --git a/core/java/android/credentials/CreateCredentialException.java b/core/java/android/credentials/CreateCredentialException.java
index 84cc9a8..c344004 100644
--- a/core/java/android/credentials/CreateCredentialException.java
+++ b/core/java/android/credentials/CreateCredentialException.java
@@ -40,13 +40,13 @@
             "android.credentials.CreateCredentialException.TYPE_UNKNOWN";
 
     /**
-     * The error type value for when no credential is available for the given {@link
-     * CredentialManager#createCredential(CreateCredentialRequest, Activity,
+     * The error type value for when no create options are available from any provider(s),
+     * for the given {@link CredentialManager#createCredential(CreateCredentialRequest, Activity,
      * CancellationSignal, Executor, OutcomeReceiver)} request.
      */
     @NonNull
-    public static final String TYPE_NO_CREDENTIAL =
-            "android.credentials.CreateCredentialException.TYPE_NO_CREDENTIAL";
+    public static final String TYPE_NO_CREATE_OPTIONS =
+            "android.credentials.CreateCredentialException.TYPE_NO_CREATE_OPTIONS";
     /**
      * The error type value for when the user intentionally cancelled the request.
      *
diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java
index 8b43a21..54909aa 100644
--- a/core/java/android/credentials/CredentialManager.java
+++ b/core/java/android/credentials/CredentialManager.java
@@ -16,6 +16,8 @@
 
 package android.credentials;
 
+import static android.Manifest.permission.CREDENTIAL_MANAGER_SET_ORIGIN;
+
 import static java.util.Objects.requireNonNull;
 
 import android.annotation.CallbackExecutor;
@@ -124,6 +126,57 @@
     }
 
     /**
+     * Launches the necessary flows to retrieve an app credential from the user, for the given
+     * origin.
+     *
+     * <p>The execution can potentially launch UI flows to collect user consent to using a
+     * credential, display a picker when multiple credentials exist, etc.
+     *
+     * @param request the request specifying type(s) of credentials to get from the user
+     * @param origin the origin of the calling app. Callers of this special API (e.g. browsers)
+     * can set this origin for an app different from their own, to be able to get credentials
+     * on behalf of that app.
+     * @param activity the activity used to launch any UI needed
+     * @param cancellationSignal an optional signal that allows for cancelling this call
+     * @param executor the callback will take place on this {@link Executor}
+     * @param callback the callback invoked when the request succeeds or fails
+     */
+    @RequiresPermission(CREDENTIAL_MANAGER_SET_ORIGIN)
+    public void getCredentialWithOrigin(
+            @NonNull GetCredentialRequest request,
+            @Nullable String origin,
+            @NonNull Activity activity,
+            @Nullable CancellationSignal cancellationSignal,
+            @CallbackExecutor @NonNull Executor executor,
+            @NonNull OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback) {
+        requireNonNull(request, "request must not be null");
+        requireNonNull(activity, "activity must not be null");
+        requireNonNull(executor, "executor must not be null");
+        requireNonNull(callback, "callback must not be null");
+
+        if (cancellationSignal != null && cancellationSignal.isCanceled()) {
+            Log.w(TAG, "getCredential already canceled");
+            return;
+        }
+
+        ICancellationSignal cancelRemote = null;
+        try {
+            cancelRemote =
+                mService.executeGetCredentialWithOrigin(
+                    request,
+                    new GetCredentialTransport(activity, executor, callback),
+                    mContext.getOpPackageName(),
+                    origin);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+
+        if (cancellationSignal != null && cancelRemote != null) {
+            cancellationSignal.setRemote(cancelRemote);
+        }
+    }
+
+    /**
      * Launches the necessary flows to register an app credential for the user.
      *
      * <p>The execution can potentially launch UI flows to collect user consent to creating or
@@ -169,6 +222,58 @@
     }
 
     /**
+     * Launches the necessary flows to register an app credential for the user.
+     *
+     * <p>The execution can potentially launch UI flows to collect user consent to creating or
+     * storing the new credential, etc.
+     *
+     * @param request the request specifying type(s) of credentials to get from the user, for the
+     * given origin
+     * @param origin the origin of the calling app. Callers of this special API (e.g. browsers)
+     * can set this origin for an app different from their own, to be able to get credentials
+     * on behalf of that app.
+     * @param activity the activity used to launch any UI needed
+     * @param cancellationSignal an optional signal that allows for cancelling this call
+     * @param executor the callback will take place on this {@link Executor}
+     * @param callback the callback invoked when the request succeeds or fails
+     */
+    @RequiresPermission(CREDENTIAL_MANAGER_SET_ORIGIN)
+    public void createCredentialWithOrigin(
+            @NonNull CreateCredentialRequest request,
+            @Nullable String origin,
+            @NonNull Activity activity,
+            @Nullable CancellationSignal cancellationSignal,
+            @CallbackExecutor @NonNull Executor executor,
+            @NonNull
+            OutcomeReceiver<CreateCredentialResponse, CreateCredentialException> callback) {
+        requireNonNull(request, "request must not be null");
+        requireNonNull(activity, "activity must not be null");
+        requireNonNull(executor, "executor must not be null");
+        requireNonNull(callback, "callback must not be null");
+
+        if (cancellationSignal != null && cancellationSignal.isCanceled()) {
+            Log.w(TAG, "createCredential already canceled");
+            return;
+        }
+
+        ICancellationSignal cancelRemote = null;
+        try {
+            cancelRemote =
+                mService.executeCreateCredentialWithOrigin(
+                    request,
+                    new CreateCredentialTransport(activity, executor, callback),
+                    mContext.getOpPackageName(),
+                    origin);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+
+        if (cancellationSignal != null && cancelRemote != null) {
+            cancellationSignal.setRemote(cancelRemote);
+        }
+    }
+
+    /**
      * Clears the current user credential state from all credential providers.
      *
      * <p>You should invoked this api after your user signs out of your app to notify all credential
diff --git a/core/java/android/credentials/ICredentialManager.aidl b/core/java/android/credentials/ICredentialManager.aidl
index 885acd4..604e56b 100644
--- a/core/java/android/credentials/ICredentialManager.aidl
+++ b/core/java/android/credentials/ICredentialManager.aidl
@@ -40,8 +40,12 @@
 
     @nullable ICancellationSignal executeGetCredential(in GetCredentialRequest request, in IGetCredentialCallback callback, String callingPackage);
 
+    @nullable ICancellationSignal executeGetCredentialWithOrigin(in GetCredentialRequest request, in IGetCredentialCallback callback, String callingPackage, String origin);
+
     @nullable ICancellationSignal executeCreateCredential(in CreateCredentialRequest request, in ICreateCredentialCallback callback, String callingPackage);
 
+    @nullable ICancellationSignal executeCreateCredentialWithOrigin(in CreateCredentialRequest request, in ICreateCredentialCallback callback, String callingPackage, String origin);
+
     @nullable ICancellationSignal clearCredentialState(in ClearCredentialStateRequest request, in IClearCredentialStateCallback callback, String callingPackage);
 
     @nullable ICancellationSignal listEnabledProviders(in IListEnabledProvidersCallback callback);
diff --git a/core/java/android/credentials/ui/AuthenticationEntry.java b/core/java/android/credentials/ui/AuthenticationEntry.java
new file mode 100644
index 0000000..b1a382c
--- /dev/null
+++ b/core/java/android/credentials/ui/AuthenticationEntry.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials.ui;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.TestApi;
+import android.app.slice.Slice;
+import android.content.Intent;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.AnnotationValidations;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * An authentication entry.
+ *
+ * @hide
+ */
+@TestApi
+public final class AuthenticationEntry implements Parcelable {
+    @NonNull private final String mKey;
+    @NonNull private final String mSubkey;
+    @NonNull private final @Status int mStatus;
+    @Nullable private Intent mFrameworkExtrasIntent;
+    @NonNull private final Slice mSlice;
+
+    /** @hide **/
+    @IntDef(prefix = {"STATUS_"}, value = {
+            STATUS_LOCKED,
+            STATUS_UNLOCKED_BUT_EMPTY_LESS_RECENT,
+            STATUS_UNLOCKED_BUT_EMPTY_MOST_RECENT,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Status {}
+
+    /** This entry is still locked, as initially supplied by the provider. */
+    public static final int STATUS_LOCKED = 0;
+    /** This entry was unlocked but didn't contain any credential. Meanwhile, "less recent" means
+     *  there is another such entry that was unlocked more recently. */
+    public static final int STATUS_UNLOCKED_BUT_EMPTY_LESS_RECENT = 1;
+    /** This is the most recent entry that was unlocked but didn't contain any credential.
+     *  There should be at most one authentication entry with this status. */
+    public static final int STATUS_UNLOCKED_BUT_EMPTY_MOST_RECENT = 2;
+
+    private AuthenticationEntry(@NonNull Parcel in) {
+        mKey = in.readString8();
+        mSubkey = in.readString8();
+        mStatus = in.readInt();
+        mSlice = in.readTypedObject(Slice.CREATOR);
+        mFrameworkExtrasIntent = in.readTypedObject(Intent.CREATOR);
+
+        AnnotationValidations.validate(NonNull.class, null, mKey);
+        AnnotationValidations.validate(NonNull.class, null, mSubkey);
+        AnnotationValidations.validate(NonNull.class, null, mSlice);
+    }
+
+    /** Constructor to be used for an entry that does not require further activities
+     * to be invoked when selected.
+     */
+    public AuthenticationEntry(@NonNull String key, @NonNull String subkey, @NonNull Slice slice,
+            @Status int status) {
+        mKey = key;
+        mSubkey = subkey;
+        mSlice = slice;
+        mStatus = status;
+    }
+
+    /** Constructor to be used for an entry that requires a pending intent to be invoked
+     * when clicked.
+     */
+    public AuthenticationEntry(@NonNull String key, @NonNull String subkey, @NonNull Slice slice,
+            @Status int status, @NonNull Intent intent) {
+        this(key, subkey, slice, status);
+        mFrameworkExtrasIntent = intent;
+    }
+
+    /**
+    * Returns the identifier of this entry that's unique within the context of the CredentialManager
+    * request.
+    */
+    @NonNull
+    public String getKey() {
+        return mKey;
+    }
+
+    /**
+     * Returns the sub-identifier of this entry that's unique within the context of the {@code key}.
+     */
+    @NonNull
+    public String getSubkey() {
+        return mSubkey;
+    }
+
+    /**
+    * Returns the Slice to be rendered.
+    */
+    @NonNull
+    public Slice getSlice() {
+        return mSlice;
+    }
+
+    /**
+     * Returns the entry status.
+     */
+    @NonNull
+    @Status
+    public int getStatus() {
+        return mStatus;
+    }
+
+    @Nullable
+    @SuppressLint("IntentBuilderName") // Not building a new intent.
+    public Intent getFrameworkExtrasIntent() {
+        return mFrameworkExtrasIntent;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString8(mKey);
+        dest.writeString8(mSubkey);
+        dest.writeInt(mStatus);
+        dest.writeTypedObject(mSlice, flags);
+        dest.writeTypedObject(mFrameworkExtrasIntent, flags);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final @NonNull Creator<AuthenticationEntry> CREATOR = new Creator<>() {
+        @Override
+        public AuthenticationEntry createFromParcel(@NonNull Parcel in) {
+            return new AuthenticationEntry(in);
+        }
+
+        @Override
+        public AuthenticationEntry[] newArray(int size) {
+            return new AuthenticationEntry[size];
+        }
+    };
+}
diff --git a/core/java/android/credentials/ui/Entry.java b/core/java/android/credentials/ui/Entry.java
index 37a5724..12665ba 100644
--- a/core/java/android/credentials/ui/Entry.java
+++ b/core/java/android/credentials/ui/Entry.java
@@ -29,30 +29,12 @@
 import com.android.internal.util.AnnotationValidations;
 
 /**
- * A credential, save, or action entry to be rendered.
+ * A credential, create, or action entry to be rendered.
  *
  * @hide
  */
 @TestApi
 public final class Entry implements Parcelable {
-    /**
-    * The intent extra key for the action chip {@code Entry} list when launching the UX activities.
-    */
-    @NonNull public static final String EXTRA_ENTRY_LIST_ACTION_CHIP =
-            "android.credentials.ui.extra.ENTRY_LIST_ACTION_CHIP";
-    /**
-    * The intent extra key for the credential / save {@code Entry} list when launching the UX
-    * activities.
-    */
-    @NonNull public static final String EXTRA_ENTRY_LIST_CREDENTIAL =
-            "android.credentials.ui.extra.ENTRY_LIST_CREDENTIAL";
-    /**
-    * The intent extra key for the authentication action {@code Entry} when launching the UX
-    * activities.
-    */
-    @NonNull public static final String EXTRA_ENTRY_AUTHENTICATION_ACTION =
-            "android.credentials.ui.extra.ENTRY_AUTHENTICATION_ACTION";
-
     @NonNull private final String mKey;
     @NonNull private final String mSubkey;
     @Nullable private PendingIntent mPendingIntent;
diff --git a/core/java/android/credentials/ui/GetCredentialProviderData.java b/core/java/android/credentials/ui/GetCredentialProviderData.java
index c44d1dd..e4688a84 100644
--- a/core/java/android/credentials/ui/GetCredentialProviderData.java
+++ b/core/java/android/credentials/ui/GetCredentialProviderData.java
@@ -39,13 +39,14 @@
     @NonNull
     private final List<Entry> mActionChips;
     @NonNull
-    private final List<Entry> mAuthenticationEntries;
+    private final List<AuthenticationEntry> mAuthenticationEntries;
     @Nullable
     private final Entry mRemoteEntry;
 
     public GetCredentialProviderData(
             @NonNull String providerFlattenedComponentName, @NonNull List<Entry> credentialEntries,
-            @NonNull List<Entry> actionChips, @NonNull List<Entry> authenticationEntries,
+            @NonNull List<Entry> actionChips,
+            @NonNull List<AuthenticationEntry> authenticationEntries,
             @Nullable Entry remoteEntry) {
         super(providerFlattenedComponentName);
         mCredentialEntries = credentialEntries;
@@ -65,7 +66,7 @@
     }
 
     @NonNull
-    public List<Entry> getAuthenticationEntries() {
+    public List<AuthenticationEntry> getAuthenticationEntries() {
         return mAuthenticationEntries;
     }
 
@@ -87,8 +88,8 @@
         mActionChips = actionChips;
         AnnotationValidations.validate(NonNull.class, null, mActionChips);
 
-        List<Entry> authenticationEntries  = new ArrayList<>();
-        in.readTypedList(authenticationEntries, Entry.CREATOR);
+        List<AuthenticationEntry> authenticationEntries  = new ArrayList<>();
+        in.readTypedList(authenticationEntries, AuthenticationEntry.CREATOR);
         mAuthenticationEntries = authenticationEntries;
         AnnotationValidations.validate(NonNull.class, null, mAuthenticationEntries);
 
@@ -133,7 +134,7 @@
         @NonNull private String mProviderFlattenedComponentName;
         @NonNull private List<Entry> mCredentialEntries = new ArrayList<>();
         @NonNull private List<Entry> mActionChips = new ArrayList<>();
-        @NonNull private List<Entry> mAuthenticationEntries = new ArrayList<>();
+        @NonNull private List<AuthenticationEntry> mAuthenticationEntries = new ArrayList<>();
         @Nullable private Entry mRemoteEntry = null;
 
         /** Constructor with required properties. */
@@ -157,7 +158,8 @@
 
         /** Sets the authentication entry to be displayed to the user. */
         @NonNull
-        public Builder setAuthenticationEntries(@NonNull List<Entry> authenticationEntry) {
+        public Builder setAuthenticationEntries(
+                @NonNull List<AuthenticationEntry> authenticationEntry) {
             mAuthenticationEntries = authenticationEntry;
             return this;
         }
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index 6c3233c..fa678fc 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -188,6 +188,19 @@
         }
 
         /**
+         * Shows a default subtitle for the prompt if the subtitle would otherwise be
+         * null or empty. Currently for internal use only.
+         * @return This builder.
+         * @hide
+         */
+        @RequiresPermission(USE_BIOMETRIC_INTERNAL)
+        @NonNull
+        public Builder setUseDefaultSubtitle() {
+            mPromptInfo.setUseDefaultSubtitle(true);
+            return this;
+        }
+
+        /**
          * Optional: Sets a description that will be shown on the prompt.
          * @param description The description to display.
          * @return This builder.
@@ -629,6 +642,16 @@
     }
 
     /**
+     * Whether to use a default subtitle. For internal use only.
+     * @return See {@link Builder#setUseDefaultSubtitle()}.
+     * @hide
+     */
+    @RequiresPermission(USE_BIOMETRIC_INTERNAL)
+    public boolean shouldUseDefaultSubtitle() {
+        return mPromptInfo.isUseDefaultSubtitle();
+    }
+
+    /**
      * Gets the description for the prompt, as set by {@link Builder#setDescription(CharSequence)}.
      * @return The description for the prompt, or null if the prompt has no description.
      */
diff --git a/core/java/android/hardware/biometrics/PromptInfo.java b/core/java/android/hardware/biometrics/PromptInfo.java
index a6b8096..02aad1d 100644
--- a/core/java/android/hardware/biometrics/PromptInfo.java
+++ b/core/java/android/hardware/biometrics/PromptInfo.java
@@ -33,6 +33,7 @@
     @NonNull private CharSequence mTitle;
     private boolean mUseDefaultTitle;
     @Nullable private CharSequence mSubtitle;
+    private boolean mUseDefaultSubtitle;
     @Nullable private CharSequence mDescription;
     @Nullable private CharSequence mDeviceCredentialTitle;
     @Nullable private CharSequence mDeviceCredentialSubtitle;
@@ -56,6 +57,7 @@
         mTitle = in.readCharSequence();
         mUseDefaultTitle = in.readBoolean();
         mSubtitle = in.readCharSequence();
+        mUseDefaultSubtitle = in.readBoolean();
         mDescription = in.readCharSequence();
         mDeviceCredentialTitle = in.readCharSequence();
         mDeviceCredentialSubtitle = in.readCharSequence();
@@ -94,6 +96,7 @@
         dest.writeCharSequence(mTitle);
         dest.writeBoolean(mUseDefaultTitle);
         dest.writeCharSequence(mSubtitle);
+        dest.writeBoolean(mUseDefaultSubtitle);
         dest.writeCharSequence(mDescription);
         dest.writeCharSequence(mDeviceCredentialTitle);
         dest.writeCharSequence(mDeviceCredentialSubtitle);
@@ -128,6 +131,8 @@
             return true;
         } else if (mUseDefaultTitle) {
             return true;
+        } else if (mUseDefaultSubtitle) {
+            return true;
         } else if (mDeviceCredentialTitle != null) {
             return true;
         } else if (mDeviceCredentialSubtitle != null) {
@@ -154,6 +159,10 @@
         mSubtitle = subtitle;
     }
 
+    public void setUseDefaultSubtitle(boolean useDefaultSubtitle) {
+        mUseDefaultSubtitle = useDefaultSubtitle;
+    }
+
     public void setDescription(CharSequence description) {
         mDescription = description;
     }
@@ -227,6 +236,10 @@
         return mSubtitle;
     }
 
+    public boolean isUseDefaultSubtitle() {
+        return mUseDefaultSubtitle;
+    }
+
     public CharSequence getDescription() {
         return mDescription;
     }
diff --git a/core/java/android/hardware/input/VirtualNavigationTouchpadConfig.java b/core/java/android/hardware/input/VirtualNavigationTouchpadConfig.java
index 5ad5fd9..8935efa 100644
--- a/core/java/android/hardware/input/VirtualNavigationTouchpadConfig.java
+++ b/core/java/android/hardware/input/VirtualNavigationTouchpadConfig.java
@@ -49,12 +49,12 @@
     }
 
     /** Returns the touchpad height. */
-    public int getHeight() {
+    @IntRange(from = 1) public int getHeight() {
         return mHeight;
     }
 
     /** Returns the touchpad width. */
-    public int getWidth() {
+    @IntRange(from = 1) public int getWidth() {
         return mWidth;
     }
 
diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java
index 6dc80cf..1bb44af 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -43,7 +43,6 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.util.Log;
 
 import java.io.IOException;
@@ -426,6 +425,7 @@
     // recovery
     @UnsupportedAppUsage
     static INfcAdapter sService;
+    static NfcServiceManager.ServiceRegisterer sServiceRegisterer;
     static INfcTag sTagService;
     static INfcCardEmulation sCardEmulationService;
     static INfcFCardEmulation sNfcFCardEmulationService;
@@ -624,6 +624,12 @@
                 Log.v(TAG, "this device does not have NFC support");
                 throw new UnsupportedOperationException();
             }
+            NfcServiceManager manager = NfcFrameworkInitializer.getNfcServiceManager();
+            if (manager == null) {
+                Log.e(TAG, "NfcServiceManager is null");
+                throw new UnsupportedOperationException();
+            }
+            sServiceRegisterer = manager.getNfcManagerServiceRegisterer();
             sService = getServiceInterface();
             if (sService == null) {
                 Log.e(TAG, "could not retrieve NFC service");
@@ -665,7 +671,7 @@
     /** get handle to NFC service interface */
     private static INfcAdapter getServiceInterface() {
         /* get a handle to NFC service */
-        IBinder b = ServiceManager.getService("nfc");
+        IBinder b = sServiceRegisterer.get();
         if (b == null) {
             return null;
         }
@@ -695,12 +701,13 @@
                     "context not associated with any application (using a mock context?)");
         }
 
-        if (getServiceInterface() == null) {
-            // NFC is not available
-            return null;
+        if (sIsInitialized && sServiceRegisterer.tryGet() == null) {
+            synchronized (NfcAdapter.class) {
+                /* Stale sService pointer */
+                if (sIsInitialized) sIsInitialized = false;
+            }
         }
-
-        /* use getSystemService() for consistency */
+        /* Try to initialize the service */
         NfcManager manager = (NfcManager) context.getSystemService(Context.NFC_SERVICE);
         if (manager == null) {
             // NFC not available
diff --git a/core/java/android/nfc/NfcFrameworkInitializer.java b/core/java/android/nfc/NfcFrameworkInitializer.java
new file mode 100644
index 0000000..1ab8a1e
--- /dev/null
+++ b/core/java/android/nfc/NfcFrameworkInitializer.java
@@ -0,0 +1,71 @@
+/*
+ * 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 android.nfc;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.app.SystemServiceRegistry;
+import android.content.Context;
+
+/**
+ * Class for performing registration for Nfc service.
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+public class NfcFrameworkInitializer {
+    private NfcFrameworkInitializer() {}
+
+    private static volatile NfcServiceManager sNfcServiceManager;
+
+    /**
+     * Sets an instance of {@link NfcServiceManager} that allows
+     * the nfc mainline module to register/obtain nfc binder services. This is called
+     * by the platform during the system initialization.
+     *
+     * @param nfcServiceManager instance of {@link NfcServiceManager} that allows
+     * the nfc mainline module to register/obtain nfcd binder services.
+     */
+    public static void setNfcServiceManager(
+            @NonNull NfcServiceManager nfcServiceManager) {
+        if (sNfcServiceManager != null) {
+            throw new IllegalStateException("setNfcServiceManager called twice!");
+        }
+
+        if (nfcServiceManager == null) {
+            throw new IllegalArgumentException("nfcServiceManager must not be null");
+        }
+
+        sNfcServiceManager = nfcServiceManager;
+    }
+
+    /** @hide */
+    public static NfcServiceManager getNfcServiceManager() {
+        return sNfcServiceManager;
+    }
+
+    /**
+     * Called by {@link SystemServiceRegistry}'s static initializer and registers NFC service
+     * to {@link Context}, so that {@link Context#getSystemService} can return them.
+     *
+     * @throws IllegalStateException if this is called from anywhere besides
+     * {@link SystemServiceRegistry}
+     */
+    public static void registerServiceWrappers() {
+        SystemServiceRegistry.registerContextAwareService(Context.NFC_SERVICE,
+                NfcManager.class, context -> new NfcManager(context));
+    }
+}
diff --git a/core/java/android/nfc/NfcServiceManager.java b/core/java/android/nfc/NfcServiceManager.java
new file mode 100644
index 0000000..5582f11
--- /dev/null
+++ b/core/java/android/nfc/NfcServiceManager.java
@@ -0,0 +1,126 @@
+/*
+ * 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.
+ */
+
+
+/**********************************************************************
+ * This file is not a part of the NFC mainline modure                 *
+ * *******************************************************************/
+
+package android.nfc;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.SystemApi.Client;
+import android.content.Context;
+import android.os.IBinder;
+import android.os.ServiceManager;
+
+/**
+ * Provides a way to register and obtain the system service binder objects managed by the nfc
+ * service.
+ *
+ * @hide
+ */
+@SystemApi(client = Client.MODULE_LIBRARIES)
+public class NfcServiceManager {
+
+    /**
+     * @hide
+     */
+    public NfcServiceManager() {
+    }
+
+    /**
+     * A class that exposes the methods to register and obtain each system service.
+     */
+    public static final class ServiceRegisterer {
+        private final String mServiceName;
+
+        /**
+         * @hide
+         */
+        public ServiceRegisterer(String serviceName) {
+            mServiceName = serviceName;
+        }
+
+        /**
+         * Register a system server binding object for a service.
+         */
+        public void register(@NonNull IBinder service) {
+            ServiceManager.addService(mServiceName, service);
+        }
+
+        /**
+         * Get the system server binding object for a service.
+         *
+         * <p>This blocks until the service instance is ready,
+         * or a timeout happens, in which case it returns null.
+         */
+        @Nullable
+        public IBinder get() {
+            return ServiceManager.getService(mServiceName);
+        }
+
+        /**
+         * Get the system server binding object for a service.
+         *
+         * <p>This blocks until the service instance is ready,
+         * or a timeout happens, in which case it throws {@link ServiceNotFoundException}.
+         */
+        @NonNull
+        public IBinder getOrThrow() throws ServiceNotFoundException {
+            try {
+                return ServiceManager.getServiceOrThrow(mServiceName);
+            } catch (ServiceManager.ServiceNotFoundException e) {
+                throw new ServiceNotFoundException(mServiceName);
+            }
+        }
+
+        /**
+         * Get the system server binding object for a service. If the specified service is
+         * not available, it returns null.
+         */
+        @Nullable
+        public IBinder tryGet() {
+            return ServiceManager.checkService(mServiceName);
+        }
+    }
+
+    /**
+     * See {@link ServiceRegisterer#getOrThrow}.
+     *
+     */
+    public static class ServiceNotFoundException extends ServiceManager.ServiceNotFoundException {
+        /**
+         * Constructor.
+         *
+         * @param name the name of the binder service that cannot be found.
+         *
+         */
+        public ServiceNotFoundException(@NonNull String name) {
+            super(name);
+        }
+    }
+
+    /**
+     * Returns {@link ServiceRegisterer} for the "nfc" service.
+     */
+    @NonNull
+    public ServiceRegisterer getNfcManagerServiceRegisterer() {
+        return new ServiceRegisterer(Context.NFC_SERVICE);
+    }
+}
diff --git a/core/java/android/os/ExternalVibration.java b/core/java/android/os/ExternalVibration.java
index db5bc70..fb115b3 100644
--- a/core/java/android/os/ExternalVibration.java
+++ b/core/java/android/os/ExternalVibration.java
@@ -104,6 +104,10 @@
         return mAttrs;
     }
 
+    public IBinder getToken() {
+        return mToken;
+    }
+
     public VibrationAttributes getVibrationAttributes() {
         return new VibrationAttributes.Builder(mAttrs).build();
     }
diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
index 7ad1735..93d5082 100644
--- a/core/java/android/os/ParcelFileDescriptor.java
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -34,7 +34,6 @@
 import android.annotation.NonNull;
 import android.annotation.SuppressLint;
 import android.annotation.TestApi;
-import android.app.ActivityThread;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.BroadcastReceiver;
 import android.content.ContentProvider;
@@ -333,13 +332,10 @@
     private static FileDescriptor openInternal(File file, int mode) throws FileNotFoundException {
         if ((mode & MODE_WRITE_ONLY) != 0 && (mode & MODE_APPEND) == 0
                 && (mode & MODE_TRUNCATE) == 0 && ((mode & MODE_READ_ONLY) == 0)
-                && file.exists()) {
-            String packageName = ActivityThread.currentApplication().getApplicationContext()
-                    .getPackageName();
+                && file != null && file.exists()) {
             Slog.wtfQuiet(TAG, "ParcelFileDescriptor.open is called with w without t or a or r, "
                     + "which will have a different behavior beginning in Android Q."
-                    + "\nPackage Name: " + packageName + "\nMode: " + mode
-                    + "\nFilename: " + file.getPath());
+                    + "\nMode: " + mode + "\nFilename: " + file.getPath());
         }
 
         final int flags = FileUtils.translateModePfdToPosix(mode) | ifAtLeastQ(O_CLOEXEC);
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index cf68572..709f221 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -599,7 +599,7 @@
 
     /**
      * Activity Action: Show settings to allow configuration of
-     * {@link Manifest.permission#RUN_LONG_JOBS} permission
+     * {@link Manifest.permission#RUN_USER_INITIATED_JOBS} permission
      *
      * Input: Optionally, the Intent's data URI can specify the application package name to
      * directly invoke the management GUI specific to the package name. For example
diff --git a/core/java/android/service/autofill/Dataset.java b/core/java/android/service/autofill/Dataset.java
index 0fa5e3e..d943bf9 100644
--- a/core/java/android/service/autofill/Dataset.java
+++ b/core/java/android/service/autofill/Dataset.java
@@ -18,6 +18,7 @@
 
 import static android.view.autofill.Helper.sDebug;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
@@ -33,6 +34,8 @@
 
 import com.android.internal.util.Preconditions;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Objects;
 import java.util.regex.Pattern;
@@ -112,6 +115,55 @@
  * </ol>
  */
 public final class Dataset implements Parcelable {
+    /**
+     * This dataset is picked because of unknown reason.
+     * @hide
+     */
+    public static final int PICK_REASON_UNKNOWN = 0;
+    /**
+     * This dataset is picked because of autofill provider detection was chosen.
+     * @hide
+     */
+    public static final int PICK_REASON_AUTOFILL_PROVIDER_DETECTION = 1;
+    /**
+     * This dataset is picked because of PCC detection was chosen.
+     * @hide
+     */
+    public static final int PICK_REASON_PCC_DETECTION = 2;
+    /**
+     * This dataset is picked because of Framework detection was chosen.
+     * @hide
+     */
+    public static final int PICK_REASON_FRAMEWORK_DETECTION = 3;
+    /**
+     * This dataset is picked because of Autofill Provider being a fallback.
+     * @hide
+     */
+    public static final int PICK_REASON_AUTOFILL_PROVIDER_FALLBACK = 4;
+    /**
+     * This dataset is picked because of PCC detection being a fallback.
+     * @hide
+     */
+    public static final int PICK_REASON_PCC_DETECTION_FALLBACK = 5;
+    /**
+     * This dataset is picked because of Framework detection being a fallback.
+     * @hide
+     */
+    public static final int PICK_REASON_FRAMEWORK_FALLBACK = 6;
+
+    @IntDef(prefix = { "PICK_REASON_" }, value = {
+            PICK_REASON_UNKNOWN,
+            PICK_REASON_AUTOFILL_PROVIDER_DETECTION,
+            PICK_REASON_PCC_DETECTION,
+            PICK_REASON_FRAMEWORK_DETECTION,
+            PICK_REASON_AUTOFILL_PROVIDER_FALLBACK,
+            PICK_REASON_PCC_DETECTION_FALLBACK,
+            PICK_REASON_FRAMEWORK_FALLBACK,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface DatasetEligibleReason{}
+
+    private @DatasetEligibleReason int mEligibleReason;
 
     private final ArrayList<AutofillId> mFieldIds;
     private final ArrayList<AutofillValue> mFieldValues;
@@ -130,6 +182,67 @@
     private final IntentSender mAuthentication;
     @Nullable String mId;
 
+    /**
+     * Constructor to copy the dataset, but replaces the AutofillId with the given input.
+     * Useful to modify the field type, and provide autofillId.
+     * @hide
+     */
+    public Dataset(
+            ArrayList<AutofillId> fieldIds,
+            ArrayList<AutofillValue> fieldValues,
+            ArrayList<RemoteViews> fieldPresentations,
+            ArrayList<RemoteViews> fieldDialogPresentations,
+            ArrayList<InlinePresentation> fieldInlinePresentations,
+            ArrayList<InlinePresentation> fieldInlineTooltipPresentations,
+            ArrayList<DatasetFieldFilter> fieldFilters,
+            ArrayList<String> autofillDatatypes,
+            ClipData fieldContent,
+            RemoteViews presentation,
+            RemoteViews dialogPresentation,
+            @Nullable InlinePresentation inlinePresentation,
+            @Nullable  InlinePresentation inlineTooltipPresentation,
+            @Nullable String id,
+            IntentSender authentication) {
+        mFieldIds = fieldIds;
+        mFieldValues = fieldValues;
+        mFieldPresentations = fieldPresentations;
+        mFieldDialogPresentations = fieldDialogPresentations;
+        mFieldInlinePresentations = fieldInlinePresentations;
+        mFieldInlineTooltipPresentations = fieldInlineTooltipPresentations;
+        mAutofillDatatypes = autofillDatatypes;
+        mFieldFilters = fieldFilters;
+        mFieldContent = fieldContent;
+        mPresentation = presentation;
+        mDialogPresentation = dialogPresentation;
+        mInlinePresentation = inlinePresentation;
+        mInlineTooltipPresentation = inlineTooltipPresentation;
+        mAuthentication = authentication;
+        mId = id;
+    }
+
+    /**
+     * Constructor to copy the dataset, but replaces the AutofillId with the given input.
+     * Useful to modify the field type, and provide autofillId.
+     * @hide
+     */
+    public Dataset(Dataset dataset, ArrayList<AutofillId> ids) {
+        mFieldIds = ids;
+        mFieldValues = dataset.mFieldValues;
+        mFieldPresentations = dataset.mFieldPresentations;
+        mFieldDialogPresentations = dataset.mFieldDialogPresentations;
+        mFieldInlinePresentations = dataset.mFieldInlinePresentations;
+        mFieldInlineTooltipPresentations = dataset.mFieldInlineTooltipPresentations;
+        mFieldFilters = dataset.mFieldFilters;
+        mFieldContent = dataset.mFieldContent;
+        mPresentation = dataset.mPresentation;
+        mDialogPresentation = dataset.mDialogPresentation;
+        mInlinePresentation = dataset.mInlinePresentation;
+        mInlineTooltipPresentation = dataset.mInlineTooltipPresentation;
+        mAuthentication = dataset.mAuthentication;
+        mId = dataset.mId;
+        mAutofillDatatypes = dataset.mAutofillDatatypes;
+    }
+
     private Dataset(Builder builder) {
         mFieldIds = builder.mFieldIds;
         mFieldValues = builder.mFieldValues;
@@ -292,6 +405,22 @@
     }
 
     /**
+     * Sets the reason as to why this dataset is eligible
+     * @hide
+     */
+    public void setEligibleReasonReason(@DatasetEligibleReason int eligibleReason) {
+        this.mEligibleReason = eligibleReason;
+    }
+
+    /**
+     * Get the reason as to why this dataset is eligible.
+     * @hide
+     */
+    public @DatasetEligibleReason int getEligibleReason() {
+        return mEligibleReason;
+    }
+
+    /**
      * A builder for {@link Dataset} objects. You must provide at least
      * one value for a field or set an authentication intent.
      */
@@ -1147,6 +1276,7 @@
         parcel.writeParcelable(mFieldContent, flags);
         parcel.writeParcelable(mAuthentication, flags);
         parcel.writeString(mId);
+        parcel.writeInt(mEligibleReason);
     }
 
     public static final @NonNull Creator<Dataset> CREATOR = new Creator<Dataset>() {
@@ -1181,6 +1311,7 @@
             final IntentSender authentication = parcel.readParcelable(null,
                     android.content.IntentSender.class);
             final String datasetId = parcel.readString();
+            final int eligibleReason = parcel.readInt();
 
             // Always go through the builder to ensure the data ingested by
             // the system obeys the contract of the builder to avoid attacks
@@ -1243,7 +1374,9 @@
             }
             builder.setAuthentication(authentication);
             builder.setId(datasetId);
-            return builder.build();
+            Dataset dataset = builder.build();
+            dataset.mEligibleReason = eligibleReason;
+            return dataset;
         }
 
         @Override
diff --git a/core/java/android/service/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java
index fa7ace3..c962bf1 100644
--- a/core/java/android/service/autofill/FillResponse.java
+++ b/core/java/android/service/autofill/FillResponse.java
@@ -117,6 +117,80 @@
     private final boolean mShowSaveDialogIcon;
     private final @Nullable FieldClassification[] mDetectedFieldTypes;
 
+    /**
+    * Creates a shollow copy of the provided FillResponse.
+    *
+    * @hide
+    */
+    public static FillResponse shallowCopy(FillResponse r, List<Dataset> datasets) {
+        return new FillResponse(
+                (datasets != null) ? new ParceledListSlice<>(datasets) : null,
+                r.mSaveInfo,
+                r.mClientState,
+                r.mPresentation,
+                r.mInlinePresentation,
+                r.mInlineTooltipPresentation,
+                r.mDialogPresentation,
+                r.mDialogHeader,
+                r.mHeader,
+                r.mFooter,
+                r.mAuthentication,
+                r.mAuthenticationIds,
+                r.mIgnoredIds,
+                r.mFillDialogTriggerIds,
+                r.mDisableDuration,
+                r.mFieldClassificationIds,
+                r.mFlags,
+                r.mRequestId,
+                r.mUserData,
+                r.mCancelIds,
+                r.mSupportsInlineSuggestions,
+                r.mIconResourceId,
+                r.mServiceDisplayNameResourceId,
+                r.mShowFillDialogIcon,
+                r.mShowSaveDialogIcon,
+                r.mDetectedFieldTypes);
+    }
+
+    private FillResponse(ParceledListSlice<Dataset> datasets, SaveInfo saveInfo, Bundle clientState,
+            RemoteViews presentation, InlinePresentation inlinePresentation,
+            InlinePresentation inlineTooltipPresentation, RemoteViews dialogPresentation,
+            RemoteViews dialogHeader, RemoteViews header, RemoteViews footer,
+            IntentSender authentication, AutofillId[] authenticationIds, AutofillId[] ignoredIds,
+            AutofillId[] fillDialogTriggerIds, long disableDuration,
+            AutofillId[] fieldClassificationIds, int flags, int requestId, UserData userData,
+            int[] cancelIds, boolean supportsInlineSuggestions, int iconResourceId,
+            int serviceDisplayNameResourceId, boolean showFillDialogIcon,
+            boolean showSaveDialogIcon,
+            FieldClassification[] detectedFieldTypes) {
+        mDatasets = datasets;
+        mSaveInfo = saveInfo;
+        mClientState = clientState;
+        mPresentation = presentation;
+        mInlinePresentation = inlinePresentation;
+        mInlineTooltipPresentation = inlineTooltipPresentation;
+        mDialogPresentation = dialogPresentation;
+        mDialogHeader = dialogHeader;
+        mHeader = header;
+        mFooter = footer;
+        mAuthentication = authentication;
+        mAuthenticationIds = authenticationIds;
+        mIgnoredIds = ignoredIds;
+        mFillDialogTriggerIds = fillDialogTriggerIds;
+        mDisableDuration = disableDuration;
+        mFieldClassificationIds = fieldClassificationIds;
+        mFlags = flags;
+        mRequestId = requestId;
+        mUserData = userData;
+        mCancelIds = cancelIds;
+        mSupportsInlineSuggestions = supportsInlineSuggestions;
+        mIconResourceId = iconResourceId;
+        mServiceDisplayNameResourceId = serviceDisplayNameResourceId;
+        mShowFillDialogIcon = showFillDialogIcon;
+        mShowSaveDialogIcon = showSaveDialogIcon;
+        mDetectedFieldTypes = detectedFieldTypes;
+    }
+
     private FillResponse(@NonNull Builder builder) {
         mDatasets = (builder.mDatasets != null) ? new ParceledListSlice<>(builder.mDatasets) : null;
         mSaveInfo = builder.mSaveInfo;
@@ -674,6 +748,15 @@
         }
 
         /**
+         * @hide
+         */
+        @NonNull
+        public Builder setDatasets(ArrayList<Dataset> dataset) {
+            mDatasets = dataset;
+            return this;
+        }
+
+        /**
          * Sets the {@link SaveInfo} associated with this response.
          *
          * @return This builder.
diff --git a/core/java/android/service/credentials/BeginCreateCredentialRequest.java b/core/java/android/service/credentials/BeginCreateCredentialRequest.java
index e9f7c8a6..2eed998 100644
--- a/core/java/android/service/credentials/BeginCreateCredentialRequest.java
+++ b/core/java/android/service/credentials/BeginCreateCredentialRequest.java
@@ -25,8 +25,6 @@
 
 import com.android.internal.util.Preconditions;
 
-import java.util.Objects;
-
 /**
  * Request for beginning a create credential request.
  *
@@ -54,7 +52,9 @@
             @Nullable CallingAppInfo callingAppInfo) {
         mType = Preconditions.checkStringNotEmpty(type,
                 "type must not be null or empty");
-        mData = Objects.requireNonNull(data, "data must not be null");
+        Bundle dataCopy = new Bundle();
+        dataCopy.putAll(data);
+        mData = dataCopy;
         mCallingAppInfo = callingAppInfo;
     }
 
diff --git a/core/java/android/service/credentials/BeginCreateCredentialResponse.java b/core/java/android/service/credentials/BeginCreateCredentialResponse.java
index 8ca3a1a..f0f954d 100644
--- a/core/java/android/service/credentials/BeginCreateCredentialResponse.java
+++ b/core/java/android/service/credentials/BeginCreateCredentialResponse.java
@@ -34,6 +34,14 @@
     private final @NonNull List<CreateEntry> mCreateEntries;
     private final @Nullable CreateEntry mRemoteCreateEntry;
 
+    /**
+     * Creates an empty response instance, to be used when there are no {@link CreateEntry}
+     * to return.
+     */
+    public BeginCreateCredentialResponse() {
+        this(/*createEntries=*/new ArrayList<>(), /*remoteCreateEntry=*/null);
+    }
+
     private BeginCreateCredentialResponse(@NonNull Parcel in) {
         List<CreateEntry> createEntries = new ArrayList<>();
         in.readTypedList(createEntries, CreateEntry.CREATOR);
@@ -137,13 +145,8 @@
 
         /**
          * Builds a new instance of {@link BeginCreateCredentialResponse}.
-         *
-         * @throws NullPointerException If {@code createEntries} is null.
-         * @throws IllegalArgumentException If {@code createEntries} is empty.
          */
         public @NonNull BeginCreateCredentialResponse build() {
-            Preconditions.checkCollectionNotEmpty(mCreateEntries, "createEntries must "
-                    + "not be null, or empty");
             return new BeginCreateCredentialResponse(mCreateEntries, mRemoteCreateEntry);
         }
     }
diff --git a/core/java/android/service/credentials/BeginGetCredentialOption.java b/core/java/android/service/credentials/BeginGetCredentialOption.java
index 1df908a..81b9f22 100644
--- a/core/java/android/service/credentials/BeginGetCredentialOption.java
+++ b/core/java/android/service/credentials/BeginGetCredentialOption.java
@@ -16,8 +16,6 @@
 
 package android.service.credentials;
 
-import static java.util.Objects.requireNonNull;
-
 import android.annotation.NonNull;
 import android.annotation.SuppressLint;
 import android.os.Bundle;
@@ -39,6 +37,8 @@
  */
 @SuppressLint("ParcelNotFinal")
 public class BeginGetCredentialOption implements Parcelable {
+    private static final String BUNDLE_ID_KEY =
+            "android.service.credentials.BeginGetCredentialOption.BUNDLE_ID_KEY";
     /**
      * A unique id associated with this request option.
      */
@@ -58,11 +58,7 @@
     private final Bundle mCandidateQueryData;
 
     /**
-     * Returns the unique id associated with this request. Providers must pass this id
-     * to the constructor of {@link CredentialEntry} while creating a candidate credential
-     * entry for this request option.
-     *
-     * @hide
+     * Returns the unique id associated with this request. This is for internal use only.
      */
     @NonNull
     public String getId() {
@@ -127,8 +123,14 @@
             @NonNull Bundle candidateQueryData) {
         mId = id;
         mType = Preconditions.checkStringNotEmpty(type, "type must not be empty");
-        mCandidateQueryData = requireNonNull(
-                candidateQueryData, "candidateQueryData must not be null");
+        Bundle bundle = new Bundle();
+        bundle.putAll(candidateQueryData);
+        mCandidateQueryData = bundle;
+        addIdToBundle();
+    }
+
+    private void addIdToBundle() {
+        mCandidateQueryData.putString(BUNDLE_ID_KEY, mId);
     }
 
     private BeginGetCredentialOption(@NonNull Parcel in) {
diff --git a/core/java/android/service/credentials/BeginGetCredentialResponse.java b/core/java/android/service/credentials/BeginGetCredentialResponse.java
index 0f64c63..3652742 100644
--- a/core/java/android/service/credentials/BeginGetCredentialResponse.java
+++ b/core/java/android/service/credentials/BeginGetCredentialResponse.java
@@ -44,6 +44,17 @@
     /** Remote credential entry to get the response from a different device. */
     private final @Nullable CredentialEntry mRemoteCredentialEntry;
 
+    /**
+     * Creates an empty response instance, to be used when there are no {@link CredentialEntry},
+     * or {@link Action} to return.
+     */
+    public BeginGetCredentialResponse() {
+        this(/*credentialEntries=*/new ArrayList<>(),
+                /*authenticationActions=*/new ArrayList<>(),
+                /*actions=*/new ArrayList<>(),
+                /*remoteCredentialEntry=*/null);
+    }
+
     private BeginGetCredentialResponse(@NonNull List<CredentialEntry> credentialEntries,
             @NonNull List<Action> authenticationEntries, @NonNull List<Action> actions,
             @Nullable CredentialEntry remoteCredentialEntry) {
@@ -243,16 +254,8 @@
 
         /**
          * Builds a {@link BeginGetCredentialResponse} instance.
-         *
-         * @throws IllegalStateException if {@code credentialEntries}, {@code actions}
-         *                               and {@code remoteCredentialEntry} are all null or empty.
          */
         public @NonNull BeginGetCredentialResponse build() {
-            if (mCredentialEntries.isEmpty() && mActions.isEmpty()
-                    && mRemoteCredentialEntry == null && mAuthenticationEntries.isEmpty()) {
-                throw new IllegalStateException("must set either an authentication, "
-                        + "credential, action or remote entry");
-            }
             return new BeginGetCredentialResponse(mCredentialEntries, mAuthenticationEntries,
                     mActions, mRemoteCredentialEntry);
         }
diff --git a/core/java/android/service/credentials/CredentialProviderInfo.java b/core/java/android/service/credentials/CredentialProviderInfo.java
index 6a10a6a..ce8bd0c 100644
--- a/core/java/android/service/credentials/CredentialProviderInfo.java
+++ b/core/java/android/service/credentials/CredentialProviderInfo.java
@@ -175,7 +175,8 @@
                         serviceInfo.packageName,
                         PackageManager.ApplicationInfoFlags.of(PackageManager.MATCH_SYSTEM_ONLY));
                 if (appInfo != null
-                        && context.checkPermission(Manifest.permission.SYSTEM_CREDENTIAL_PROVIDER,
+                        && context.checkPermission(
+                                Manifest.permission.PROVIDE_DEFAULT_ENABLED_CREDENTIAL_SERVICE,
                         /*pId=*/-1, appInfo.uid) == PackageManager.PERMISSION_GRANTED) {
                     services.add(new CredentialProviderInfo(context, serviceInfo,
                             /*isSystemProvider=*/true));
diff --git a/core/java/android/service/voice/VoiceInteractionManagerInternal.java b/core/java/android/service/voice/VoiceInteractionManagerInternal.java
index c47fdd3..270f848 100644
--- a/core/java/android/service/voice/VoiceInteractionManagerInternal.java
+++ b/core/java/android/service/voice/VoiceInteractionManagerInternal.java
@@ -39,7 +39,7 @@
      * @param options A Bundle of private arguments to the current voice interaction service
      */
     public abstract void startLocalVoiceInteraction(@NonNull IBinder callingActivity,
-            @Nullable String attributionTag, @NonNull Bundle options);
+            @Nullable String attributionTag, @Nullable Bundle options);
 
     /**
      * Returns whether the currently selected voice interaction service supports local voice
diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java
index 0d51395..5778518 100644
--- a/core/java/android/service/voice/VoiceInteractionSession.java
+++ b/core/java/android/service/voice/VoiceInteractionSession.java
@@ -1781,11 +1781,13 @@
      * Intent.EXTRA_TIME ("android.intent.extra.TIME") indicating timing
      * in milliseconds of the KeyEvent that triggered Assistant and
      * Intent.EXTRA_ASSIST_INPUT_DEVICE_ID (android.intent.extra.ASSIST_INPUT_DEVICE_ID)
-     *  referring to the device that sent the request.
+     *  referring to the device that sent the request. Starting from Android 14, the system will
+     * add {@link VoiceInteractionService#KEY_SHOW_SESSION_ID}, the Bundle is not null. But the
+     * application should handle null case before Android 14.
      * @param showFlags The show flags originally provided to
      * {@link VoiceInteractionService#showSession VoiceInteractionService.showSession}.
      */
-    public void onShow(Bundle args, int showFlags) {
+    public void onShow(@Nullable Bundle args, int showFlags) {
     }
 
     /**
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 10138cf..ba9847e 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -218,7 +218,7 @@
         DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_TRACKPAD_GESTURE, "false");
         DEFAULT_FLAGS.put(SETTINGS_ENABLE_SPA, "true");
         DEFAULT_FLAGS.put(SETTINGS_ADB_METRICS_WRITER, "false");
-        DEFAULT_FLAGS.put(SETTINGS_SHOW_STYLUS_PREFERENCES, "false");
+        DEFAULT_FLAGS.put(SETTINGS_SHOW_STYLUS_PREFERENCES, "true");
         DEFAULT_FLAGS.put(SETTINGS_BIOMETRICS2_ENROLLMENT, "false");
         DEFAULT_FLAGS.put(SETTINGS_ACCESSIBILITY_HEARING_AID_PAGE, "false");
         DEFAULT_FLAGS.put(SETTINGS_PREFER_ACCESSIBILITY_MENU_IN_SYSTEM, "false");
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 8663013..8e09411 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -173,6 +173,7 @@
             boolean isTrustedOverlay);
     private static native void nativeSetDropInputMode(
             long transactionObj, long nativeObject, int flags);
+    private static native void nativeSurfaceFlushJankData(long nativeSurfaceObject);
     private static native boolean nativeClearContentFrameStats(long nativeObject);
     private static native boolean nativeGetContentFrameStats(long nativeObject, WindowContentFrameStats outStats);
     private static native boolean nativeClearAnimationFrameStats();
@@ -479,6 +480,14 @@
 
     private WeakReference<View> mLocalOwnerView;
 
+    // A throwable with the stack filled when this SurfaceControl is released (only if
+    // sDebugUsageAfterRelease) is enabled
+    private Throwable mReleaseStack = null;
+
+    // Triggers the stack to be saved when any SurfaceControl in this process is released, which can
+    // be dumped as additional context
+    private static volatile boolean sDebugUsageAfterRelease = false;
+
     static GlobalTransactionWrapper sGlobalTransaction;
     static long sTransactionNestCount = 0;
 
@@ -751,6 +760,11 @@
         }
         mNativeObject = nativeObject;
         mNativeHandle = mNativeObject != 0 ? nativeGetHandle(nativeObject) : 0;
+        if (sDebugUsageAfterRelease && mNativeObject == 0) {
+            mReleaseStack = new Throwable("Assigned invalid nativeObject");
+        } else {
+            mReleaseStack = null;
+        }
     }
 
     /**
@@ -1246,6 +1260,9 @@
 
     @Override
     public void writeToParcel(Parcel dest, int flags) {
+        if (sDebugUsageAfterRelease) {
+            checkNotReleased();
+        }
         dest.writeString8(mName);
         dest.writeInt(mWidth);
         dest.writeInt(mHeight);
@@ -1262,6 +1279,18 @@
     }
 
     /**
+     * Enables additional debug logs to track usage-after-release of all SurfaceControls in this
+     * process.
+     * @hide
+     */
+    public static void setDebugUsageAfterRelease(boolean debug) {
+        if (!Build.isDebuggable()) {
+            return;
+        }
+        sDebugUsageAfterRelease = debug;
+    }
+
+    /**
      * Checks whether two {@link SurfaceControl} objects represent the same surface.
      *
      * @param other The other object to check
@@ -1382,6 +1411,9 @@
             mFreeNativeResources.run();
             mNativeObject = 0;
             mNativeHandle = 0;
+            if (sDebugUsageAfterRelease) {
+                mReleaseStack = new Throwable("Released");
+            }
             mCloseGuard.close();
             synchronized (mChoreographerLock) {
                 if (mChoreographer != null) {
@@ -1403,8 +1435,15 @@
     }
 
     private void checkNotReleased() {
-        if (mNativeObject == 0) throw new NullPointerException(
-                "Invalid " + this + ", mNativeObject is null. Have you called release() already?");
+        if (mNativeObject == 0) {
+            if (mReleaseStack != null) {
+                throw new IllegalStateException("Invalid usage after release of " + this,
+                        mReleaseStack);
+            } else {
+                throw new NullPointerException("mNativeObject of " + this
+                        + " is null. Have you called release() already?");
+            }
+        }
     }
 
     /**
@@ -2738,7 +2777,7 @@
          */
         @NonNull
         public Transaction setFrameRateSelectionPriority(@NonNull SurfaceControl sc, int priority) {
-            sc.checkNotReleased();
+            checkPreconditions(sc);
             nativeSetFrameRateSelectionPriority(mNativeObject, sc.mNativeObject, priority);
             return this;
         }
@@ -3847,6 +3886,15 @@
         }
 
         /**
+         * Sends a flush jank data transaction for the given surface.
+         * @hide
+         */
+        public static void sendSurfaceFlushJankData(SurfaceControl sc) {
+            sc.checkNotReleased();
+            nativeSurfaceFlushJankData(sc.mNativeObject);
+        }
+
+        /**
          * @hide
          */
         public void sanitize() {
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index 7969518..d405c0b 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -255,17 +255,6 @@
      */
     public static final int FLAG_CONTENT_CONTROLS = 4;
 
-
-    /**
-     * {@link ComponentName} for the Accessibility Menu {@link AccessibilityService} as provided
-     * inside the system build, used for automatic migration to this version of the service.
-     * @hide
-     */
-    public static final ComponentName ACCESSIBILITY_MENU_IN_SYSTEM =
-            new ComponentName("com.android.systemui.accessibility.accessibilitymenu",
-                    "com.android.systemui.accessibility.accessibilitymenu"
-                            + ".AccessibilityMenuService");
-
     @UnsupportedAppUsage
     static final Object sInstanceSync = new Object();
 
diff --git a/core/java/android/view/animation/AnimationUtils.java b/core/java/android/view/animation/AnimationUtils.java
index c8c910d..7d1dc76 100644
--- a/core/java/android/view/animation/AnimationUtils.java
+++ b/core/java/android/view/animation/AnimationUtils.java
@@ -28,7 +28,6 @@
 import android.os.SystemClock;
 import android.util.AttributeSet;
 import android.util.Xml;
-import android.view.InflateException;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -138,9 +137,16 @@
         try {
             parser = context.getResources().getAnimation(id);
             return createAnimationFromXml(context, parser);
-        } catch (XmlPullParserException | IOException | InflateException ex) {
-            throw new NotFoundException(
-                    "Can't load animation resource ID #0x" + Integer.toHexString(id), ex);
+        } catch (XmlPullParserException ex) {
+            NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" +
+                    Integer.toHexString(id));
+            rnf.initCause(ex);
+            throw rnf;
+        } catch (IOException ex) {
+            NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" +
+                    Integer.toHexString(id));
+            rnf.initCause(ex);
+            throw rnf;
         } finally {
             if (parser != null) parser.close();
         }
@@ -153,9 +159,8 @@
     }
 
     @UnsupportedAppUsage
-    private static Animation createAnimationFromXml(
-            Context c, XmlPullParser parser, AnimationSet parent, AttributeSet attrs)
-            throws XmlPullParserException, IOException, InflateException {
+    private static Animation createAnimationFromXml(Context c, XmlPullParser parser,
+            AnimationSet parent, AttributeSet attrs) throws XmlPullParserException, IOException {
 
         Animation anim = null;
 
@@ -163,8 +168,8 @@
         int type;
         int depth = parser.getDepth();
 
-        while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
-                && type != XmlPullParser.END_DOCUMENT) {
+        while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
+               && type != XmlPullParser.END_DOCUMENT) {
 
             if (type != XmlPullParser.START_TAG) {
                 continue;
@@ -188,7 +193,7 @@
             } else if (name.equals("extend")) {
                 anim = new ExtendAnimation(c, attrs);
             } else {
-                throw new InflateException("Unknown animation name: " + parser.getName());
+                throw new RuntimeException("Unknown animation name: " + parser.getName());
             }
 
             if (parent != null) {
@@ -215,24 +220,29 @@
         try {
             parser = context.getResources().getAnimation(id);
             return createLayoutAnimationFromXml(context, parser);
-        } catch (XmlPullParserException | IOException | InflateException ex) {
-            throw new NotFoundException(
-                    "Can't load animation resource ID #0x" + Integer.toHexString(id), ex);
+        } catch (XmlPullParserException ex) {
+            NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" +
+                    Integer.toHexString(id));
+            rnf.initCause(ex);
+            throw rnf;
+        } catch (IOException ex) {
+            NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" +
+                    Integer.toHexString(id));
+            rnf.initCause(ex);
+            throw rnf;
         } finally {
             if (parser != null) parser.close();
         }
     }
 
-    private static LayoutAnimationController createLayoutAnimationFromXml(
-            Context c, XmlPullParser parser)
-            throws XmlPullParserException, IOException, InflateException {
+    private static LayoutAnimationController createLayoutAnimationFromXml(Context c,
+            XmlPullParser parser) throws XmlPullParserException, IOException {
 
         return createLayoutAnimationFromXml(c, parser, Xml.asAttributeSet(parser));
     }
 
-    private static LayoutAnimationController createLayoutAnimationFromXml(
-            Context c, XmlPullParser parser, AttributeSet attrs)
-            throws XmlPullParserException, IOException, InflateException {
+    private static LayoutAnimationController createLayoutAnimationFromXml(Context c,
+            XmlPullParser parser, AttributeSet attrs) throws XmlPullParserException, IOException {
 
         LayoutAnimationController controller = null;
 
@@ -253,7 +263,7 @@
             } else if ("gridLayoutAnimation".equals(name)) {
                 controller = new GridLayoutAnimationController(c, attrs);
             } else {
-                throw new InflateException("Unknown layout animation name: " + name);
+                throw new RuntimeException("Unknown layout animation name: " + name);
             }
         }
 
@@ -332,9 +342,16 @@
         try {
             parser = context.getResources().getAnimation(id);
             return createInterpolatorFromXml(context.getResources(), context.getTheme(), parser);
-        } catch (XmlPullParserException | IOException | InflateException ex) {
-            throw new NotFoundException(
-                    "Can't load animation resource ID #0x" + Integer.toHexString(id), ex);
+        } catch (XmlPullParserException ex) {
+            NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" +
+                    Integer.toHexString(id));
+            rnf.initCause(ex);
+            throw rnf;
+        } catch (IOException ex) {
+            NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" +
+                    Integer.toHexString(id));
+            rnf.initCause(ex);
+            throw rnf;
         } finally {
             if (parser != null) parser.close();
         }
@@ -355,20 +372,25 @@
         try {
             parser = res.getAnimation(id);
             return createInterpolatorFromXml(res, theme, parser);
-        } catch (XmlPullParserException | IOException | InflateException ex) {
-            throw new NotFoundException(
-                    "Can't load animation resource ID #0x" + Integer.toHexString(id), ex);
+        } catch (XmlPullParserException ex) {
+            NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" +
+                    Integer.toHexString(id));
+            rnf.initCause(ex);
+            throw rnf;
+        } catch (IOException ex) {
+            NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" +
+                    Integer.toHexString(id));
+            rnf.initCause(ex);
+            throw rnf;
         } finally {
-            if (parser != null) {
+            if (parser != null)
                 parser.close();
-            }
         }
 
     }
 
-    private static Interpolator createInterpolatorFromXml(
-            Resources res, Theme theme, XmlPullParser parser)
-            throws XmlPullParserException, IOException, InflateException {
+    private static Interpolator createInterpolatorFromXml(Resources res, Theme theme, XmlPullParser parser)
+            throws XmlPullParserException, IOException {
 
         BaseInterpolator interpolator = null;
 
@@ -408,7 +430,7 @@
             } else if (name.equals("pathInterpolator")) {
                 interpolator = new PathInterpolator(res, theme, attrs);
             } else {
-                throw new InflateException("Unknown interpolator name: " + parser.getName());
+                throw new RuntimeException("Unknown interpolator name: " + parser.getName());
             }
         }
         return interpolator;
diff --git a/core/java/android/view/autofill/AutofillFeatureFlags.java b/core/java/android/view/autofill/AutofillFeatureFlags.java
index f3a8e85..6d78e60 100644
--- a/core/java/android/view/autofill/AutofillFeatureFlags.java
+++ b/core/java/android/view/autofill/AutofillFeatureFlags.java
@@ -82,12 +82,6 @@
             "autofill_dialog_enabled";
 
     /**
-     * Indicates that PCC Autofill detection feature is enabled or not.
-     */
-    public static final String DEVICE_CONFIG_AUTOFILL_PCC_FEATURE_PROVIDER_HINTS =
-            "pcc_classification_hints";
-
-    /**
      * Sets the autofill hints allowed list for the fields that can trigger the fill dialog
      * feature at Activity starting.
      *
@@ -184,6 +178,28 @@
     public static final String DEVICE_CONFIG_AUTOFILL_PCC_CLASSIFICATION_ENABLED =
             "pcc_classification_enabled";
 
+    /**
+     * Give preference to autofill provider's detection.
+     * @hide
+     */
+    public static final String DEVICE_CONFIG_PREFER_PROVIDER_OVER_PCC = "prefer_provider_over_pcc";
+
+    /**
+     * Indicates the Autofill Hints that would be requested by the service from the Autofill
+     * Provider.
+     */
+    public static final String DEVICE_CONFIG_AUTOFILL_PCC_FEATURE_PROVIDER_HINTS =
+            "pcc_classification_hints";
+
+    /**
+     * Use data from secondary source if primary not present .
+     * For eg: if we prefer PCC over provider, and PCC detection didn't classify a field, however,
+     * autofill provider did, this flag would decide whether we use that result, and show some
+     * presentation for that particular field.
+     * @hide
+     */
+    public static final String DEVICE_CONFIG_PCC_USE_FALLBACK = "pcc_use_fallback";
+
     // END AUTOFILL PCC CLASSIFICATION FLAGS
 
 
@@ -196,11 +212,9 @@
             "autofill_inline_tooltip_first_show_delay";
 
     private static final String DIALOG_HINTS_DELIMITER = ":";
-    private static final String PCC_HINTS_DELIMITER = ",";
 
     private static final boolean DEFAULT_HAS_FILL_DIALOG_UI_FEATURE = false;
     private static final String DEFAULT_FILL_DIALOG_ENABLED_HINTS = "";
-    private static final String DEFAULT_PCC_FEATURE_PROVIDER_HINTS = "";
 
 
     // CREDENTIAL MANAGER DEFAULTS
@@ -214,7 +228,8 @@
 
     // AUTOFILL PCC CLASSIFICATION FLAGS DEFAULTS
     // Default for whether the pcc classification is enabled for autofill.
-    private static final boolean DEFAULT_AUTOFILL_PCC_CLASSIFICATION_ENABLED = false;
+    /** @hide */
+    public static final boolean DEFAULT_AUTOFILL_PCC_CLASSIFICATION_ENABLED = false;
     // END AUTOFILL PCC CLASSIFICATION FLAGS DEFAULTS
 
 
@@ -233,25 +248,6 @@
     }
 
     /**
-     * The list of datatypes that is supported by framework
-     * detection.
-     *
-     * @hide
-     */
-    public static String[] getTypeHintsForProvider() {
-        final String typeHints = DeviceConfig.getString(
-                DeviceConfig.NAMESPACE_AUTOFILL,
-                DEVICE_CONFIG_AUTOFILL_PCC_FEATURE_PROVIDER_HINTS,
-                DEFAULT_PCC_FEATURE_PROVIDER_HINTS);
-        if (TextUtils.isEmpty(typeHints)) {
-            return new String[0];
-        }
-
-        return ArrayUtils.filter(typeHints.split(PCC_HINTS_DELIMITER), String[]::new,
-                (str) -> !TextUtils.isEmpty(str));
-    }
-
-    /**
      * Gets fill dialog enabled hints.
      *
      * @hide
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 136846a..3f452f8 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -124,13 +124,11 @@
 import android.view.animation.LinearInterpolator;
 import android.view.inputmethod.CorrectionInfo;
 import android.view.inputmethod.CursorAnchorInfo;
-import android.view.inputmethod.EditorBoundsInfo;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.ExtractedText;
 import android.view.inputmethod.ExtractedTextRequest;
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputMethodManager;
-import android.view.inputmethod.TextAppearanceInfo;
 import android.view.textclassifier.TextClassification;
 import android.view.textclassifier.TextClassificationManager;
 import android.widget.AdapterView.OnItemClickListener;
@@ -4667,7 +4665,7 @@
      * {@link InputMethodManager#isWatchingCursor(View)} returns false.
      */
     private final class CursorAnchorInfoNotifier implements TextViewPositionListener {
-        final CursorAnchorInfo.Builder mSelectionInfoBuilder = new CursorAnchorInfo.Builder();
+        final CursorAnchorInfo.Builder mCursorAnchorInfoBuilder = new CursorAnchorInfo.Builder();
         final Matrix mViewToScreenMatrix = new Matrix();
 
         @Override
@@ -4687,165 +4685,21 @@
             // Skip if the IME has not requested the cursor/anchor position.
             final int knownCursorAnchorInfoModes =
                     InputConnection.CURSOR_UPDATE_IMMEDIATE | InputConnection.CURSOR_UPDATE_MONITOR;
-            if ((mInputMethodState.mUpdateCursorAnchorInfoMode & knownCursorAnchorInfoModes) == 0) {
+            if ((ims.mUpdateCursorAnchorInfoMode & knownCursorAnchorInfoModes) == 0) {
                 return;
             }
-            Layout layout = mTextView.getLayout();
-            if (layout == null) {
-                return;
+
+            final CursorAnchorInfo cursorAnchorInfo =
+                    mTextView.getCursorAnchorInfo(ims.mUpdateCursorAnchorInfoFilter,
+                            mCursorAnchorInfoBuilder, mViewToScreenMatrix);
+
+            if (cursorAnchorInfo != null) {
+                imm.updateCursorAnchorInfo(mTextView, cursorAnchorInfo);
+
+                // Drop the immediate flag if any.
+                mInputMethodState.mUpdateCursorAnchorInfoMode &=
+                        ~InputConnection.CURSOR_UPDATE_IMMEDIATE;
             }
-            final int filter = mInputMethodState.mUpdateCursorAnchorInfoFilter;
-            boolean includeEditorBounds =
-                    (filter & InputConnection.CURSOR_UPDATE_FILTER_EDITOR_BOUNDS) != 0;
-            boolean includeCharacterBounds =
-                    (filter & InputConnection.CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS) != 0;
-            boolean includeInsertionMarker =
-                    (filter & InputConnection.CURSOR_UPDATE_FILTER_INSERTION_MARKER) != 0;
-            boolean includeVisibleLineBounds =
-                    (filter & InputConnection.CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS) != 0;
-            boolean includeTextAppearance =
-                    (filter & InputConnection.CURSOR_UPDATE_FILTER_TEXT_APPEARANCE) != 0;
-            boolean includeAll =
-                    (!includeEditorBounds && !includeCharacterBounds && !includeInsertionMarker
-                    && !includeVisibleLineBounds && !includeTextAppearance);
-
-            includeEditorBounds |= includeAll;
-            includeCharacterBounds |= includeAll;
-            includeInsertionMarker |= includeAll;
-            includeVisibleLineBounds |= includeAll;
-            includeTextAppearance |= includeAll;
-
-            final CursorAnchorInfo.Builder builder = mSelectionInfoBuilder;
-            builder.reset();
-
-            final int selectionStart = mTextView.getSelectionStart();
-            builder.setSelectionRange(selectionStart, mTextView.getSelectionEnd());
-
-            // Construct transformation matrix from view local coordinates to screen coordinates.
-            mViewToScreenMatrix.reset();
-            mTextView.transformMatrixToGlobal(mViewToScreenMatrix);
-            builder.setMatrix(mViewToScreenMatrix);
-
-            if (includeEditorBounds) {
-                final RectF editorBounds = new RectF();
-                editorBounds.set(0 /* left */, 0 /* top */,
-                        mTextView.getWidth(), mTextView.getHeight());
-                final RectF handwritingBounds = new RectF(
-                        -mTextView.getHandwritingBoundsOffsetLeft(),
-                        -mTextView.getHandwritingBoundsOffsetTop(),
-                        mTextView.getWidth() + mTextView.getHandwritingBoundsOffsetRight(),
-                        mTextView.getHeight() + mTextView.getHandwritingBoundsOffsetBottom());
-                EditorBoundsInfo.Builder boundsBuilder = new EditorBoundsInfo.Builder();
-                EditorBoundsInfo editorBoundsInfo = boundsBuilder.setEditorBounds(editorBounds)
-                        .setHandwritingBounds(handwritingBounds).build();
-                builder.setEditorBoundsInfo(editorBoundsInfo);
-            }
-
-            if (includeCharacterBounds || includeInsertionMarker || includeVisibleLineBounds) {
-                final float viewportToContentHorizontalOffset =
-                        mTextView.viewportToContentHorizontalOffset();
-                final float viewportToContentVerticalOffset =
-                        mTextView.viewportToContentVerticalOffset();
-                final boolean isTextTransformed = (mTextView.getTransformationMethod() != null
-                        && mTextView.getTransformed() instanceof OffsetMapping);
-                if (includeCharacterBounds && !isTextTransformed) {
-                    final CharSequence text = mTextView.getText();
-                    if (text instanceof Spannable) {
-                        final Spannable sp = (Spannable) text;
-                        int composingTextStart = EditableInputConnection.getComposingSpanStart(sp);
-                        int composingTextEnd = EditableInputConnection.getComposingSpanEnd(sp);
-                        if (composingTextEnd < composingTextStart) {
-                            final int temp = composingTextEnd;
-                            composingTextEnd = composingTextStart;
-                            composingTextStart = temp;
-                        }
-                        final boolean hasComposingText =
-                                (0 <= composingTextStart) && (composingTextStart
-                                        < composingTextEnd);
-                        if (hasComposingText) {
-                            final CharSequence composingText = text.subSequence(composingTextStart,
-                                    composingTextEnd);
-                            builder.setComposingText(composingTextStart, composingText);
-                            mTextView.populateCharacterBounds(builder, composingTextStart,
-                                    composingTextEnd, viewportToContentHorizontalOffset,
-                                    viewportToContentVerticalOffset);
-                        }
-                    }
-                }
-
-                if (includeInsertionMarker) {
-                    // Treat selectionStart as the insertion point.
-                    if (0 <= selectionStart) {
-                        final int offsetTransformed = mTextView.originalToTransformed(
-                                selectionStart, OffsetMapping.MAP_STRATEGY_CURSOR);
-                        final int line = layout.getLineForOffset(offsetTransformed);
-                        final float insertionMarkerX =
-                                layout.getPrimaryHorizontal(offsetTransformed)
-                                        + viewportToContentHorizontalOffset;
-                        final float insertionMarkerTop = layout.getLineTop(line)
-                                + viewportToContentVerticalOffset;
-                        final float insertionMarkerBaseline = layout.getLineBaseline(line)
-                                + viewportToContentVerticalOffset;
-                        final float insertionMarkerBottom =
-                                layout.getLineBottom(line, /* includeLineSpacing= */ false)
-                                        + viewportToContentVerticalOffset;
-                        final boolean isTopVisible = mTextView
-                                .isPositionVisible(insertionMarkerX, insertionMarkerTop);
-                        final boolean isBottomVisible = mTextView
-                                .isPositionVisible(insertionMarkerX, insertionMarkerBottom);
-                        int insertionMarkerFlags = 0;
-                        if (isTopVisible || isBottomVisible) {
-                            insertionMarkerFlags |= CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION;
-                        }
-                        if (!isTopVisible || !isBottomVisible) {
-                            insertionMarkerFlags |= CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION;
-                        }
-                        if (layout.isRtlCharAt(offsetTransformed)) {
-                            insertionMarkerFlags |= CursorAnchorInfo.FLAG_IS_RTL;
-                        }
-                        builder.setInsertionMarkerLocation(insertionMarkerX, insertionMarkerTop,
-                                insertionMarkerBaseline, insertionMarkerBottom,
-                                insertionMarkerFlags);
-                    }
-                }
-
-                if (includeVisibleLineBounds) {
-                    final Rect visibleRect = new Rect();
-                    if (mTextView.getContentVisibleRect(visibleRect)) {
-                        // Subtract the viewportToContentVerticalOffset to convert the view
-                        // coordinates to layout coordinates.
-                        final float visibleTop =
-                                visibleRect.top - viewportToContentVerticalOffset;
-                        final float visibleBottom =
-                                visibleRect.bottom - viewportToContentVerticalOffset;
-                        final int firstLine =
-                                layout.getLineForVertical((int) Math.floor(visibleTop));
-                        final int lastLine =
-                                layout.getLineForVertical((int) Math.ceil(visibleBottom));
-
-                        for (int line = firstLine; line <= lastLine; ++line) {
-                            final float left = layout.getLineLeft(line)
-                                    + viewportToContentHorizontalOffset;
-                            final float top = layout.getLineTop(line)
-                                    + viewportToContentVerticalOffset;
-                            final float right = layout.getLineRight(line)
-                                    + viewportToContentHorizontalOffset;
-                            final float bottom = layout.getLineBottom(line, false)
-                                    + viewportToContentVerticalOffset;
-                            builder.addVisibleLineBounds(left, top, right, bottom);
-                        }
-                    }
-                }
-            }
-
-            if (includeTextAppearance) {
-                builder.setTextAppearanceInfo(TextAppearanceInfo.createFromTextView(mTextView));
-            }
-            imm.updateCursorAnchorInfo(mTextView, builder.build());
-
-            // Drop the immediate flag if any.
-            mInputMethodState.mUpdateCursorAnchorInfoMode &=
-                    ~InputConnection.CURSOR_UPDATE_IMMEDIATE;
         }
     }
 
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 77df1f1..626df48 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -196,6 +196,7 @@
 import android.view.inputmethod.CursorAnchorInfo;
 import android.view.inputmethod.DeleteGesture;
 import android.view.inputmethod.DeleteRangeGesture;
+import android.view.inputmethod.EditorBoundsInfo;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.ExtractedText;
 import android.view.inputmethod.ExtractedTextRequest;
@@ -209,6 +210,7 @@
 import android.view.inputmethod.RemoveSpaceGesture;
 import android.view.inputmethod.SelectGesture;
 import android.view.inputmethod.SelectRangeGesture;
+import android.view.inputmethod.TextAppearanceInfo;
 import android.view.inputmethod.TextBoundsInfo;
 import android.view.inspector.InspectableProperty;
 import android.view.inspector.InspectableProperty.EnumEntry;
@@ -13658,7 +13660,7 @@
      * @return true if at least part of the text content is visible; false if the text content is
      * completely clipped or translated out of the visible area.
      */
-    boolean getContentVisibleRect(Rect rect) {
+    private boolean getContentVisibleRect(Rect rect) {
         if (!getLocalVisibleRect(rect)) {
             return false;
         }
@@ -13744,6 +13746,176 @@
     }
 
     /**
+     * Compute {@link CursorAnchorInfo} from this {@link TextView}.
+     *
+     * @param filter the {@link CursorAnchorInfo} update filter which specified the needed
+     *               information from IME.
+     * @param cursorAnchorInfoBuilder a cached {@link CursorAnchorInfo.Builder} object used to build
+     *                                the result {@link CursorAnchorInfo}.
+     * @param viewToScreenMatrix a cached {@link Matrix} object used to compute the view to screen
+     *                           matrix.
+     * @return the result {@link CursorAnchorInfo} to be passed to IME.
+     * @hide
+     */
+    @VisibleForTesting
+    @Nullable
+    public CursorAnchorInfo getCursorAnchorInfo(@InputConnection.CursorUpdateFilter int filter,
+            @NonNull CursorAnchorInfo.Builder cursorAnchorInfoBuilder,
+            @NonNull Matrix viewToScreenMatrix) {
+        Layout layout = getLayout();
+        if (layout == null) {
+            return null;
+        }
+        boolean includeEditorBounds =
+                (filter & InputConnection.CURSOR_UPDATE_FILTER_EDITOR_BOUNDS) != 0;
+        boolean includeCharacterBounds =
+                (filter & InputConnection.CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS) != 0;
+        boolean includeInsertionMarker =
+                (filter & InputConnection.CURSOR_UPDATE_FILTER_INSERTION_MARKER) != 0;
+        boolean includeVisibleLineBounds =
+                (filter & InputConnection.CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS) != 0;
+        boolean includeTextAppearance =
+                (filter & InputConnection.CURSOR_UPDATE_FILTER_TEXT_APPEARANCE) != 0;
+        boolean includeAll =
+                (!includeEditorBounds && !includeCharacterBounds && !includeInsertionMarker
+                        && !includeVisibleLineBounds && !includeTextAppearance);
+
+        includeEditorBounds |= includeAll;
+        includeCharacterBounds |= includeAll;
+        includeInsertionMarker |= includeAll;
+        includeVisibleLineBounds |= includeAll;
+        includeTextAppearance |= includeAll;
+
+        final CursorAnchorInfo.Builder builder = cursorAnchorInfoBuilder;
+        builder.reset();
+
+        final int selectionStart = getSelectionStart();
+        builder.setSelectionRange(selectionStart, getSelectionEnd());
+
+        // Construct transformation matrix from view local coordinates to screen coordinates.
+        viewToScreenMatrix.reset();
+        transformMatrixToGlobal(viewToScreenMatrix);
+        builder.setMatrix(viewToScreenMatrix);
+
+        if (includeEditorBounds) {
+            final RectF editorBounds = new RectF();
+            editorBounds.set(0 /* left */, 0 /* top */,
+                    getWidth(), getHeight());
+            final RectF handwritingBounds = new RectF(
+                    -getHandwritingBoundsOffsetLeft(),
+                    -getHandwritingBoundsOffsetTop(),
+                    getWidth() + getHandwritingBoundsOffsetRight(),
+                    getHeight() + getHandwritingBoundsOffsetBottom());
+            EditorBoundsInfo.Builder boundsBuilder = new EditorBoundsInfo.Builder();
+            EditorBoundsInfo editorBoundsInfo = boundsBuilder.setEditorBounds(editorBounds)
+                    .setHandwritingBounds(handwritingBounds).build();
+            builder.setEditorBoundsInfo(editorBoundsInfo);
+        }
+
+        if (includeCharacterBounds || includeInsertionMarker || includeVisibleLineBounds) {
+            final float viewportToContentHorizontalOffset =
+                    viewportToContentHorizontalOffset();
+            final float viewportToContentVerticalOffset =
+                    viewportToContentVerticalOffset();
+            final boolean isTextTransformed = (getTransformationMethod() != null
+                    && getTransformed() instanceof OffsetMapping);
+            if (includeCharacterBounds && !isTextTransformed) {
+                final CharSequence text = getText();
+                if (text instanceof Spannable) {
+                    final Spannable sp = (Spannable) text;
+                    int composingTextStart = EditableInputConnection.getComposingSpanStart(sp);
+                    int composingTextEnd = EditableInputConnection.getComposingSpanEnd(sp);
+                    if (composingTextEnd < composingTextStart) {
+                        final int temp = composingTextEnd;
+                        composingTextEnd = composingTextStart;
+                        composingTextStart = temp;
+                    }
+                    final boolean hasComposingText =
+                            (0 <= composingTextStart) && (composingTextStart
+                                    < composingTextEnd);
+                    if (hasComposingText) {
+                        final CharSequence composingText = text.subSequence(composingTextStart,
+                                composingTextEnd);
+                        builder.setComposingText(composingTextStart, composingText);
+                        populateCharacterBounds(builder, composingTextStart,
+                                composingTextEnd, viewportToContentHorizontalOffset,
+                                viewportToContentVerticalOffset);
+                    }
+                }
+            }
+
+            if (includeInsertionMarker) {
+                // Treat selectionStart as the insertion point.
+                if (0 <= selectionStart) {
+                    final int offsetTransformed = originalToTransformed(
+                            selectionStart, OffsetMapping.MAP_STRATEGY_CURSOR);
+                    final int line = layout.getLineForOffset(offsetTransformed);
+                    final float insertionMarkerX =
+                            layout.getPrimaryHorizontal(offsetTransformed)
+                                    + viewportToContentHorizontalOffset;
+                    final float insertionMarkerTop = layout.getLineTop(line)
+                            + viewportToContentVerticalOffset;
+                    final float insertionMarkerBaseline = layout.getLineBaseline(line)
+                            + viewportToContentVerticalOffset;
+                    final float insertionMarkerBottom =
+                            layout.getLineBottom(line, /* includeLineSpacing= */ false)
+                                    + viewportToContentVerticalOffset;
+                    final boolean isTopVisible =
+                            isPositionVisible(insertionMarkerX, insertionMarkerTop);
+                    final boolean isBottomVisible =
+                            isPositionVisible(insertionMarkerX, insertionMarkerBottom);
+                    int insertionMarkerFlags = 0;
+                    if (isTopVisible || isBottomVisible) {
+                        insertionMarkerFlags |= CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION;
+                    }
+                    if (!isTopVisible || !isBottomVisible) {
+                        insertionMarkerFlags |= CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION;
+                    }
+                    if (layout.isRtlCharAt(offsetTransformed)) {
+                        insertionMarkerFlags |= CursorAnchorInfo.FLAG_IS_RTL;
+                    }
+                    builder.setInsertionMarkerLocation(insertionMarkerX, insertionMarkerTop,
+                            insertionMarkerBaseline, insertionMarkerBottom,
+                            insertionMarkerFlags);
+                }
+            }
+
+            if (includeVisibleLineBounds) {
+                final Rect visibleRect = new Rect();
+                if (getContentVisibleRect(visibleRect)) {
+                    // Subtract the viewportToContentVerticalOffset to convert the view
+                    // coordinates to layout coordinates.
+                    final float visibleTop =
+                            visibleRect.top - viewportToContentVerticalOffset;
+                    final float visibleBottom =
+                            visibleRect.bottom - viewportToContentVerticalOffset;
+                    final int firstLine =
+                            layout.getLineForVertical((int) Math.floor(visibleTop));
+                    final int lastLine =
+                            layout.getLineForVertical((int) Math.ceil(visibleBottom));
+
+                    for (int line = firstLine; line <= lastLine; ++line) {
+                        final float left = layout.getLineLeft(line)
+                                + viewportToContentHorizontalOffset;
+                        final float top = layout.getLineTop(line)
+                                + viewportToContentVerticalOffset;
+                        final float right = layout.getLineRight(line)
+                                + viewportToContentHorizontalOffset;
+                        final float bottom = layout.getLineBottom(line, false)
+                                + viewportToContentVerticalOffset;
+                        builder.addVisibleLineBounds(left, top, right, bottom);
+                    }
+                }
+            }
+        }
+
+        if (includeTextAppearance) {
+            builder.setTextAppearanceInfo(TextAppearanceInfo.createFromTextView(this));
+        }
+        return builder.build();
+    }
+
+    /**
      * Creates the {@link TextBoundsInfo} for the text lines that intersects with the {@code rectF}.
      * @hide
      */
diff --git a/core/java/android/window/TaskFragmentInfo.java b/core/java/android/window/TaskFragmentInfo.java
index dc60edd..a881a05 100644
--- a/core/java/android/window/TaskFragmentInfo.java
+++ b/core/java/android/window/TaskFragmentInfo.java
@@ -23,7 +23,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.TestApi;
-import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
 import android.graphics.Point;
 import android.graphics.Rect;
@@ -89,9 +88,9 @@
     private final boolean mIsClearedForReorderActivityToFront;
 
     /**
-     * The maximum {@link ActivityInfo.WindowLayout#minWidth} and
-     * {@link ActivityInfo.WindowLayout#minHeight} aggregated from the TaskFragment's child
-     * activities.
+     * The maximum {@link android.content.pm.ActivityInfo.WindowLayout#minWidth} and
+     * {@link android.content.pm.ActivityInfo.WindowLayout#minHeight} aggregated from the
+     * TaskFragment's child activities.
      */
     @NonNull
     private final Point mMinimumDimensions = new Point();
@@ -179,7 +178,7 @@
 
     /**
      * Returns the minimum width this TaskFragment can be resized to.
-     * Client side must not {@link WindowContainerTransaction#setBounds(WindowContainerToken, Rect)}
+     * Client side must not {@link WindowContainerTransaction#setRelativeBounds}
      * that {@link Rect#width()} is shorter than the reported value.
      * @hide pending unhide
      */
@@ -189,7 +188,7 @@
 
     /**
      * Returns the minimum width this TaskFragment can be resized to.
-     * Client side must not {@link WindowContainerTransaction#setBounds(WindowContainerToken, Rect)}
+     * Client side must not {@link WindowContainerTransaction#setRelativeBounds}
      * that {@link Rect#height()} is shorter than the reported value.
      * @hide pending unhide
      */
diff --git a/core/java/android/window/TaskFragmentOperation.java b/core/java/android/window/TaskFragmentOperation.java
index 0d6a58b..413f0cc 100644
--- a/core/java/android/window/TaskFragmentOperation.java
+++ b/core/java/android/window/TaskFragmentOperation.java
@@ -38,8 +38,8 @@
 public final class TaskFragmentOperation implements Parcelable {
 
     /**
-     * Type for tracking other {@link WindowContainerTransaction} to TaskFragment that is not set
-     * through {@link TaskFragmentOperation}, such as {@link WindowContainerTransaction#setBounds}.
+     * Type for tracking other unknown TaskFragment operation that is not set through
+     * {@link TaskFragmentOperation}, such as invalid request.
      */
     public static final int OP_TYPE_UNKNOWN = -1;
 
@@ -70,6 +70,9 @@
     /** Sets the {@link TaskFragmentAnimationParams} for the given TaskFragment. */
     public static final int OP_TYPE_SET_ANIMATION_PARAMS = 8;
 
+    /** Sets the relative bounds with {@link WindowContainerTransaction#setRelativeBounds}. */
+    public static final int OP_TYPE_SET_RELATIVE_BOUNDS = 9;
+
     @IntDef(prefix = { "OP_TYPE_" }, value = {
             OP_TYPE_UNKNOWN,
             OP_TYPE_CREATE_TASK_FRAGMENT,
@@ -80,7 +83,8 @@
             OP_TYPE_CLEAR_ADJACENT_TASK_FRAGMENTS,
             OP_TYPE_REQUEST_FOCUS_ON_TASK_FRAGMENT,
             OP_TYPE_SET_COMPANION_TASK_FRAGMENT,
-            OP_TYPE_SET_ANIMATION_PARAMS
+            OP_TYPE_SET_ANIMATION_PARAMS,
+            OP_TYPE_SET_RELATIVE_BOUNDS
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface OperationType {}
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index a2fa162..a3209f6 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -308,7 +308,6 @@
     /**
      * Resizes a container by providing a bounds in its parent coordinate.
      * This is only used by {@link TaskFragmentOrganizer}.
-     * @hide
      */
     @NonNull
     public WindowContainerTransaction setRelativeBounds(
diff --git a/core/java/com/android/internal/accessibility/util/AccessibilityUtils.java b/core/java/com/android/internal/accessibility/util/AccessibilityUtils.java
index 6a976ef..3a8f427 100644
--- a/core/java/com/android/internal/accessibility/util/AccessibilityUtils.java
+++ b/core/java/com/android/internal/accessibility/util/AccessibilityUtils.java
@@ -19,11 +19,16 @@
 import static com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
 import static com.android.internal.accessibility.common.ShortcutConstants.SERVICES_SEPARATOR;
 
+import android.accessibilityservice.AccessibilityService;
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
 import android.os.Build;
 import android.os.UserHandle;
 import android.provider.Settings;
@@ -33,6 +38,8 @@
 import android.util.ArraySet;
 import android.view.accessibility.AccessibilityManager;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import libcore.util.EmptyArray;
 
 import java.lang.annotation.Retention;
@@ -40,6 +47,7 @@
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Optional;
 import java.util.Set;
 
 /**
@@ -66,6 +74,19 @@
     /** Specifies some parcelable spans has been changed. */
     public static final int PARCELABLE_SPAN = 2;
 
+    @VisibleForTesting
+    public static final String MENU_SERVICE_RELATIVE_CLASS_NAME = ".AccessibilityMenuService";
+
+    /**
+     * {@link ComponentName} for the Accessibility Menu {@link AccessibilityService} as provided
+     * inside the system build, used for automatic migration to this version of the service.
+     * @hide
+     */
+    public static final ComponentName ACCESSIBILITY_MENU_IN_SYSTEM =
+            new ComponentName("com.android.systemui.accessibility.accessibilitymenu",
+                    "com.android.systemui.accessibility.accessibilitymenu"
+                            + MENU_SERVICE_RELATIVE_CLASS_NAME);
+
     /**
      * Returns the set of enabled accessibility services for userId. If there are no
      * services, it returns the unmodifiable {@link Collections#emptySet()}.
@@ -244,4 +265,54 @@
         }
         return true;
     }
+
+    /**
+     * Finds the {@link ComponentName} of the AccessibilityMenu accessibility service that the
+     * device should be migrated off. Devices using this service should be migrated to
+     * {@link #ACCESSIBILITY_MENU_IN_SYSTEM}.
+     *
+     * <p>
+     * Requirements:
+     * <li>There are exactly two installed accessibility service components with class name
+     * {@link #MENU_SERVICE_RELATIVE_CLASS_NAME}.</li>
+     * <li>Exactly one of these components is equal to {@link #ACCESSIBILITY_MENU_IN_SYSTEM}.</li>
+     * </p>
+     *
+     * @return The {@link ComponentName} of the service that is not {@link
+     * #ACCESSIBILITY_MENU_IN_SYSTEM},
+     * or <code>null</code> if the above requirements are not met.
+     */
+    @Nullable
+    public static ComponentName getAccessibilityMenuComponentToMigrate(
+            PackageManager packageManager, int userId) {
+        final Set<ComponentName> menuComponentNames = findA11yMenuComponentNames(packageManager,
+                userId);
+        Optional<ComponentName> menuOutsideSystem = menuComponentNames.stream().filter(
+                name -> !name.equals(ACCESSIBILITY_MENU_IN_SYSTEM)).findFirst();
+        final boolean shouldMigrateToMenuInSystem = menuComponentNames.size() == 2
+                && menuComponentNames.contains(ACCESSIBILITY_MENU_IN_SYSTEM)
+                && menuOutsideSystem.isPresent();
+        return shouldMigrateToMenuInSystem ? menuOutsideSystem.get() : null;
+    }
+
+    /**
+     * Returns all {@link ComponentName}s whose class name ends in {@link
+     * #MENU_SERVICE_RELATIVE_CLASS_NAME}.
+     **/
+    private static Set<ComponentName> findA11yMenuComponentNames(
+            PackageManager packageManager, int userId) {
+        Set<ComponentName> result = new ArraySet<>();
+        final PackageManager.ResolveInfoFlags flags = PackageManager.ResolveInfoFlags.of(
+                PackageManager.MATCH_DISABLED_COMPONENTS
+                        | PackageManager.MATCH_DIRECT_BOOT_AWARE
+                        | PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
+        for (ResolveInfo resolveInfo : packageManager.queryIntentServicesAsUser(
+                new Intent(AccessibilityService.SERVICE_INTERFACE), flags, userId)) {
+            final ComponentName componentName = resolveInfo.serviceInfo.getComponentName();
+            if (componentName.getClassName().endsWith(MENU_SERVICE_RELATIVE_CLASS_NAME)) {
+                result.add(componentName);
+            }
+        }
+        return result;
+    }
 }
diff --git a/core/java/com/android/internal/app/AssistUtils.java b/core/java/com/android/internal/app/AssistUtils.java
index fc5cb4b..d4ff794 100644
--- a/core/java/com/android/internal/app/AssistUtils.java
+++ b/core/java/com/android/internal/app/AssistUtils.java
@@ -81,7 +81,7 @@
      *             IVoiceInteractionSessionShowCallback, IBinder)} instead
      */
     @Deprecated
-    public boolean showSessionForActiveService(@NonNull Bundle args, int sourceFlags,
+    public boolean showSessionForActiveService(@Nullable Bundle args, int sourceFlags,
             @Nullable IVoiceInteractionSessionShowCallback showCallback,
             @Nullable IBinder activityToken) {
         return showSessionForActiveServiceInternal(args, sourceFlags, /* attributionTag */ null,
@@ -99,7 +99,7 @@
      * @param showCallback optional callback to be notified when the session was shown
      * @param activityToken optional token of activity that needs to be on top
      */
-    public boolean showSessionForActiveService(@NonNull Bundle args, int sourceFlags,
+    public boolean showSessionForActiveService(@Nullable Bundle args, int sourceFlags,
             @Nullable String attributionTag,
             @Nullable IVoiceInteractionSessionShowCallback showCallback,
             @Nullable IBinder activityToken) {
@@ -107,7 +107,7 @@
                 activityToken);
     }
 
-    private boolean showSessionForActiveServiceInternal(@NonNull Bundle args, int sourceFlags,
+    private boolean showSessionForActiveServiceInternal(@Nullable Bundle args, int sourceFlags,
             @Nullable String attributionTag,
             @Nullable IVoiceInteractionSessionShowCallback showCallback,
             @Nullable IBinder activityToken) {
diff --git a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
index f724e55..c946db1 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
@@ -46,6 +46,38 @@
  */
 public class SystemUiSystemPropertiesFlags {
 
+    /** The teamfood flag allows multiple features to be opted into at once. */
+    public static final Flag TEAMFOOD = devFlag("persist.sysui.teamfood");
+
+    /**
+     * Flags related to notification features
+     */
+    public static final class NotificationFlags {
+
+        /**
+         * FOR DEVELOPMENT / TESTING ONLY!!!
+         * Forcibly demote *ALL* FSI notifications as if no apps have the app op permission.
+         * NOTE: enabling this implies SHOW_STICKY_HUN_FOR_DENIED_FSI in SystemUI
+         */
+        public static final Flag FSI_FORCE_DEMOTE =
+                devFlag("persist.sysui.notification.fsi_force_demote");
+
+        /** Gating the feature which shows FSI-denied notifications as Sticky HUNs */
+        public static final Flag SHOW_STICKY_HUN_FOR_DENIED_FSI =
+                devFlag("persist.sysui.notification.show_sticky_hun_for_denied_fsi");
+
+        /** Gating the ability for users to dismiss ongoing event notifications */
+        public static final Flag ALLOW_DISMISS_ONGOING =
+                devFlag("persist.sysui.notification.ongoing_dismissal");
+
+        /** Gating the redaction of OTP notifications on the lockscreen */
+        public static final Flag OTP_REDACTION =
+                devFlag("persist.sysui.notification.otp_redaction");
+
+    }
+
+    //// == End of flags.  Everything below this line is the implementation. == ////
+
     /** The interface used for resolving SystemUI SystemProperties Flags to booleans. */
     public interface FlagResolver {
         /** Is the flag enabled? */
@@ -75,33 +107,6 @@
         return MAIN_RESOLVER;
     }
 
-    /** The teamfood flag allows multiple features to be opted into at once. */
-    public static final Flag TEAMFOOD = devFlag("persist.sysui.teamfood");
-
-    /**
-     * Flags related to notification features
-     */
-    public static final class NotificationFlags {
-
-        /**
-         * FOR DEVELOPMENT / TESTING ONLY!!!
-         * Forcibly demote *ALL* FSI notifications as if no apps have the app op permission.
-         */
-        public static final Flag FSI_FORCE_DEMOTE =
-                devFlag("persist.sysui.notification.fsi_force_demote");
-
-        /** Gating the ability for users to dismiss ongoing event notifications */
-        public static final Flag ALLOW_DISMISS_ONGOING =
-                devFlag("persist.sysui.notification.ongoing_dismissal");
-
-        /** Gating the redaction of OTP notifications on the lockscreen */
-        public static final Flag OTP_REDACTION =
-                devFlag("persist.sysui.notification.otp_redaction");
-
-    }
-
-    //// == Everything below this line is the implementation == ////
-
     /**
      * Creates a flag that is enabled by default in debuggable builds.
      * It can be enabled by setting this flag's SystemProperty to 1.
diff --git a/core/java/com/android/internal/expresslog/Histogram.java b/core/java/com/android/internal/expresslog/Histogram.java
index fe950c4..db70cac 100644
--- a/core/java/com/android/internal/expresslog/Histogram.java
+++ b/core/java/com/android/internal/expresslog/Histogram.java
@@ -20,7 +20,7 @@
 
 import com.android.internal.util.FrameworkStatsLog;
 
-/** CounterHistogram encapsulates StatsD write API calls */
+/** Histogram encapsulates StatsD write API calls */
 public final class Histogram {
 
     private final long mMetricIdHash;
@@ -42,10 +42,10 @@
                 /*count*/ 1, binIndex);
     }
 
-    /** Used by CounterHistogram to map data sample to corresponding bin */
+    /** Used by Histogram to map data sample to corresponding bin */
     public interface BinOptions {
         /**
-         * Returns bins count to be used by counter histogram
+         * Returns bins count to be used by a histogram
          *
          * @return bins count used to initialize Options, including overflow & underflow bins
          * @hide
@@ -62,7 +62,7 @@
         int getBinForSample(float sample);
     }
 
-    /** Used by CounterHistogram to map data sample to corresponding bin for on uniform bins */
+    /** Used by Histogram to map data sample to corresponding bin for uniform bins */
     public static final class UniformOptions implements BinOptions {
 
         private final int mBinCount;
diff --git a/core/java/com/android/internal/jank/FrameTracker.java b/core/java/com/android/internal/jank/FrameTracker.java
index e4195d2..3226669 100644
--- a/core/java/com/android/internal/jank/FrameTracker.java
+++ b/core/java/com/android/internal/jank/FrameTracker.java
@@ -68,6 +68,9 @@
 
     private static final int MAX_LENGTH_EVENT_DESC = 20;
 
+    private static final int MAX_FLUSH_ATTEMPTS = 3;
+    private static final int FLUSH_DELAY_MILLISECOND = 60;
+
     static final int REASON_END_UNKNOWN = -1;
     static final int REASON_END_NORMAL = 0;
     static final int REASON_END_SURFACE_DESTROYED = 1;
@@ -358,11 +361,35 @@
             // will remove it when all the frame metrics in this duration are called back.
             // See onFrameMetricsAvailable for the logic of removing the observer.
             // Waiting at most 10 seconds for all callbacks to finish.
-            mWaitForFinishTimedOut = () -> {
-                Log.e(TAG, "force finish cuj because of time out:" + mSession.getName());
-                finish();
+            mWaitForFinishTimedOut = new Runnable() {
+                private int mFlushAttempts = 0;
+
+                @Override
+                public void run() {
+                    if (mWaitForFinishTimedOut == null || mMetricsFinalized) {
+                        return;
+                    }
+
+                    // Send a flush jank data transaction.
+                    if (mSurfaceControl != null && mSurfaceControl.isValid()) {
+                        SurfaceControl.Transaction.sendSurfaceFlushJankData(mSurfaceControl);
+                    }
+
+                    long delay;
+                    if (mFlushAttempts < MAX_FLUSH_ATTEMPTS) {
+                        delay = FLUSH_DELAY_MILLISECOND;
+                        mFlushAttempts++;
+                    } else {
+                        mWaitForFinishTimedOut = () -> {
+                            Log.e(TAG, "force finish cuj, time out: " + mSession.getName());
+                            finish();
+                        };
+                        delay = TimeUnit.SECONDS.toMillis(10);
+                    }
+                    getHandler().postDelayed(mWaitForFinishTimedOut, delay);
+                }
             };
-            getHandler().postDelayed(mWaitForFinishTimedOut, TimeUnit.SECONDS.toMillis(10));
+            getHandler().postDelayed(mWaitForFinishTimedOut, FLUSH_DELAY_MILLISECOND);
             notifyCujEvent(ACTION_SESSION_END);
             return true;
         }
@@ -537,11 +564,12 @@
 
     @UiThread
     private void finish() {
+        if (mMetricsFinalized || mCancelled) return;
+        mMetricsFinalized = true;
+
         getHandler().removeCallbacks(mWaitForFinishTimedOut);
         mWaitForFinishTimedOut = null;
-        if (mMetricsFinalized || mCancelled) return;
         markEvent("FT#finish#" + mJankInfos.size());
-        mMetricsFinalized = true;
 
         // The tracing has been ended, remove the observer, see if need to trigger perfetto.
         removeObservers();
diff --git a/core/java/com/android/internal/os/KernelSingleProcessCpuThreadReader.java b/core/java/com/android/internal/os/KernelSingleProcessCpuThreadReader.java
index 4d2a08a..e2c096c1 100644
--- a/core/java/com/android/internal/os/KernelSingleProcessCpuThreadReader.java
+++ b/core/java/com/android/internal/os/KernelSingleProcessCpuThreadReader.java
@@ -20,6 +20,7 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.expresslog.Counter;
 
 import java.io.IOException;
 import java.util.Arrays;
@@ -117,13 +118,16 @@
     public void startTrackingThreadCpuTimes() {
         if (!mIsTracking) {
             if (!startTrackingProcessCpuTimes(mPid, mCpuTimeInStateReader)) {
-                Slog.e(TAG, "Failed to start tracking process CPU times for " + mPid);
+                Slog.wtf(TAG, "Failed to start tracking process CPU times for " + mPid);
+                Counter.logIncrement("cpu.value_process_tracking_start_failure_count");
             }
             if (mSelectedThreadNativeTids.length > 0) {
                 if (!startAggregatingThreadCpuTimes(mSelectedThreadNativeTids,
                         mCpuTimeInStateReader)) {
-                    Slog.e(TAG, "Failed to start tracking aggregated thread CPU times for "
+                    Slog.wtf(TAG, "Failed to start tracking aggregated thread CPU times for "
                             + Arrays.toString(mSelectedThreadNativeTids));
+                    Counter.logIncrement(
+                            "cpu.value_aggregated_thread_tracking_start_failure_count");
                 }
             }
             mIsTracking = true;
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index b63041b..1875ecf 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -1942,9 +1942,9 @@
             case KeyEvent.KEYCODE_VOLUME_UP:
             case KeyEvent.KEYCODE_VOLUME_DOWN:
             case KeyEvent.KEYCODE_VOLUME_MUTE: {
-                // If we have a session send it the volume command, otherwise
-                // use the suggested stream.
-                if (mMediaController != null) {
+                // If we have a session and no active phone call send it the volume command,
+                // otherwise use the suggested stream.
+                if (mMediaController != null && !isActivePhoneCallKnown()) {
                     getMediaSessionManager().dispatchVolumeKeyEventToSessionAsSystemService(event,
                             mMediaController.getSessionToken());
                 } else {
@@ -1995,6 +1995,18 @@
         return false;
     }
 
+    private boolean isActivePhoneCallKnown() {
+        boolean isActivePhoneCallKnown = false;
+        AudioManager audioManager =
+                (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
+        int audioManagerMode = audioManager.getMode();
+        if (audioManagerMode == AudioManager.MODE_IN_CALL
+                || audioManagerMode == AudioManager.MODE_IN_COMMUNICATION) {
+            isActivePhoneCallKnown = true;
+        }
+        return isActivePhoneCallKnown;
+    }
+
     private KeyguardManager getKeyguardManager() {
         if (mKeyguardManager == null) {
             mKeyguardManager = (KeyguardManager) getContext().getSystemService(
diff --git a/core/java/com/android/internal/widget/ILockSettings.aidl b/core/java/com/android/internal/widget/ILockSettings.aidl
index 3494c9e..a646df3 100644
--- a/core/java/com/android/internal/widget/ILockSettings.aidl
+++ b/core/java/com/android/internal/widget/ILockSettings.aidl
@@ -17,6 +17,8 @@
 package com.android.internal.widget;
 
 import android.app.PendingIntent;
+import android.app.RemoteLockscreenValidationResult;
+import android.app.StartLockscreenValidationRequest;
 import android.app.trust.IStrongAuthTracker;
 import android.os.Bundle;
 import android.security.keystore.recovery.WrappedApplicationKey;
@@ -93,6 +95,8 @@
             in byte[] recoveryKeyBlob,
             in List<WrappedApplicationKey> applicationKeys);
     void closeSession(in String sessionId);
+    StartLockscreenValidationRequest startRemoteLockscreenValidation();
+    RemoteLockscreenValidationResult validateRemoteLockscreen(in byte[] encryptedCredential);
     boolean hasSecureLockScreen();
     boolean tryUnlockWithCachedUnifiedChallenge(int userId);
     void removeCachedUnifiedChallenge(int userId);
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 4d820ac..44c37dc 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -28,6 +28,8 @@
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.app.PropertyInvalidatedCache;
+import android.app.RemoteLockscreenValidationResult;
+import android.app.StartLockscreenValidationRequest;
 import android.app.admin.DevicePolicyManager;
 import android.app.admin.PasswordMetrics;
 import android.app.trust.IStrongAuthTracker;
@@ -1822,4 +1824,29 @@
     public void removeUser(@UserIdInt int userId) {
         getLockSettingsInternal().removeUser(userId);
     }
+
+   /**
+     * Starts a session to verify lockscreen credentials provided by a remote device.
+     */
+    @NonNull
+    public StartLockscreenValidationRequest startRemoteLockscreenValidation() {
+        try {
+            return getLockSettings().startRemoteLockscreenValidation();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+   /**
+     * Verifies credentials guess from a remote device.
+     */
+    @NonNull
+    public RemoteLockscreenValidationResult validateRemoteLockscreen(
+            @NonNull byte[] encryptedCredential) {
+        try {
+            return getLockSettings().validateRemoteLockscreen(encryptedCredential);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 97a0f50..5ca71b8 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -951,6 +951,11 @@
     transaction->setDropInputMode(ctrl, static_cast<gui::DropInputMode>(mode));
 }
 
+static void nativeSurfaceFlushJankData(JNIEnv* env, jclass clazz, jlong nativeObject) {
+    SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl*>(nativeObject);
+    SurfaceComposerClient::Transaction::sendSurfaceFlushJankDataTransaction(ctrl);
+}
+
 static void nativeSanitize(JNIEnv* env, jclass clazz, jlong transactionObj) {
     auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
     transaction->sanitize();
@@ -2246,6 +2251,8 @@
             (void*)nativeGetLayerId },
     {"nativeSetDropInputMode", "(JJI)V",
              (void*)nativeSetDropInputMode },
+    {"nativeSurfaceFlushJankData", "(J)V",
+            (void*)nativeSurfaceFlushJankData },
     {"nativeAddTransactionCommittedListener", "(JLandroid/view/SurfaceControl$TransactionCommittedListener;)V",
             (void*) nativeAddTransactionCommittedListener },
     {"nativeSetTrustedPresentationCallback", "(JJJLandroid/view/SurfaceControl$TrustedPresentationThresholds;)V",
diff --git a/core/proto/android/server/syncstorageengine.proto b/core/proto/android/server/syncstorageengine.proto
index 2f35a07..7fa01d8 100644
--- a/core/proto/android/server/syncstorageengine.proto
+++ b/core/proto/android/server/syncstorageengine.proto
@@ -85,4 +85,5 @@
   repeated StatusInfo status = 1;
 
   optional bool is_job_namespace_migrated = 2;
+  optional bool is_job_attribution_fixed = 3;
 }
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 0b6b0a1..12001b1 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -4036,14 +4036,19 @@
 
     <!-- Allows a system application to be registered with credential manager without
          having to be enabled by the user.
-         @hide -->
-    <permission android:name="android.permission.SYSTEM_CREDENTIAL_PROVIDER"
+         @hide @SystemApi -->
+    <permission android:name="android.permission.PROVIDE_DEFAULT_ENABLED_CREDENTIAL_SERVICE"
                 android:protectionLevel="signature|privileged" />
 
+    <!-- Allows a browser to invoke credential manager APIs on behalf of another RP.
+        <p>Protection level: normal -->
+    <permission android:name="android.permission.CREDENTIAL_MANAGER_SET_ORIGIN"
+        android:protectionLevel="normal" />
+
     <!-- Allows an application to be able to store and retrieve credentials from a remote
          device.
-         @hide -->
-    <permission android:name="android.permission.HYBRID_CREDENTIAL_PROVIDER"
+         @hide @SystemApi -->
+    <permission android:name="android.permission.PROVIDE_HYBRID_CREDENTIAL_SERVICE"
                 android:protectionLevel="signature|privileged" />
 
     <!-- ========================================= -->
@@ -5975,6 +5980,11 @@
     <permission android:name="android.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS"
                 android:protectionLevel="signature"/>
 
+    <!-- @SystemApi Allows application to verify lockscreen credentials provided by a remote device.
+         @hide -->
+    <permission android:name="android.permission.CHECK_REMOTE_LOCKSCREEN"
+        android:protectionLevel="signature|privileged" />
+
     <!-- Allows managing (adding, removing) fingerprint templates. Reserved for the system. @hide -->
     <permission android:name="android.permission.MANAGE_FINGERPRINT"
         android:protectionLevel="signature|privileged" />
@@ -7077,11 +7087,11 @@
     <permission android:name="android.permission.MANAGE_WEARABLE_SENSING_SERVICE"
                 android:protectionLevel="signature|privileged" />
 
-    <!-- Allows applications to use the long running jobs APIs. For more details
+    <!-- Allows applications to use the user-initiated jobs API. For more details
          see {@link android.app.job.JobInfo.Builder#setUserInitiated}.
          <p>Protection level: normal
      -->
-    <permission android:name="android.permission.RUN_LONG_JOBS"
+    <permission android:name="android.permission.RUN_USER_INITIATED_JOBS"
                 android:protectionLevel="normal"/>
 
     <!-- Allows an app access to the installer provided app metadata.
diff --git a/core/res/res/layout/notification_material_action_emphasized_tombstone.xml b/core/res/res/layout/notification_material_action_emphasized_tombstone.xml
new file mode 100644
index 0000000..60f10db
--- /dev/null
+++ b/core/res/res/layout/notification_material_action_emphasized_tombstone.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<com.android.internal.widget.EmphasizedNotificationButton
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    style="@style/NotificationEmphasizedAction"
+    android:id="@+id/action0"
+    android:layout_width="wrap_content"
+    android:layout_height="match_parent"
+    android:layout_marginStart="12dp"
+    android:drawablePadding="6dp"
+    android:enabled="false"
+    android:gravity="center"
+    android:singleLine="true"
+    android:ellipsize="end"
+/>
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index 0befa8b..0c2fc1d 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -150,6 +150,8 @@
     <color name="notification_default_color">#757575</color> <!-- Gray 600 -->
 
     <color name="notification_action_button_text_color">@color/notification_default_color</color>
+    <item  name="notification_action_disabled_content_alpha" format="float" type="dimen">0.38</item>
+    <item  name="notification_action_disabled_container_alpha" format="float" type="dimen">0.12</item>
 
     <color name="notification_progress_background_color">@color/notification_secondary_text_color_current</color>
 
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 3e30469..c75ee93 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2781,6 +2781,10 @@
      it can't be deleted or downgraded to non-admin status. -->
     <bool name="config_isMainUserPermanentAdmin">false</bool>
 
+    <!-- Whether switch to headless system user is allowed. If allowed,
+    headless system user can run in the foreground even though it is not a full user. -->
+    <bool name="config_canSwitchToHeadlessSystemUser">false</bool>
+
     <!-- Whether UI for multi user should be shown -->
     <bool name="config_enableMultiUserUI">false</bool>
 
@@ -4375,6 +4379,18 @@
     -->
     <string name="config_defaultCredentialManagerHybridService" translatable="false"></string>
 
+    <!-- The component name, flattened to a string, for the system's credential manager
+      provider service. This service allows credential retrieval and storage od credentials.
+
+     This service must be trusted, as it can be activated without explicit consent of the user.
+     If no service with the specified name exists on the device, there will be no user configurable
+     provider to service credential manager requests. However, credential manager system
+     services that do not require user consent, will still work.
+
+     See android.credentials.CredentialManager
+    -->
+    <string name="config_defaultCredentialProviderService" translatable="false"></string>
+
     <!-- The package name for the system's smartspace service.
      This service returns smartspace results.
 
@@ -6266,6 +6282,9 @@
     <string-array name="config_healthConnectMigrationKnownSigners">
     </string-array>
 
+    <!-- Package name of Health Connect data migrator application.  -->
+    <string name="config_healthConnectMigratorPackageName"></string>
+
     <!-- The Universal Stylus Initiative (USI) protocol version supported by each display.
          (@see https://universalstylus.org/).
 
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index f237ece..1530e3d 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -355,6 +355,7 @@
   <java-symbol type="bool" name="config_speed_up_audio_on_mt_calls" />
   <java-symbol type="bool" name="config_useFixedVolume" />
   <java-symbol type="bool" name="config_isMainUserPermanentAdmin"/>
+  <java-symbol type="bool" name="config_canSwitchToHeadlessSystemUser"/>
   <java-symbol type="bool" name="config_enableMultiUserUI"/>
   <java-symbol type="bool" name="config_enableMultipleAdmins"/>
   <java-symbol type="bool" name="config_enableNewAutoSelectNetworkUI"/>
@@ -3264,7 +3265,10 @@
   <java-symbol type="id" name="notification_action_list_margin_target" />
   <java-symbol type="dimen" name="notification_actions_padding_start"/>
   <java-symbol type="dimen" name="notification_actions_collapsed_priority_width"/>
+  <!--prefer to use disabled content and surface alpha values for disabled actions-->
   <java-symbol type="dimen" name="notification_action_disabled_alpha" />
+  <java-symbol type="dimen" name="notification_action_disabled_content_alpha" />
+  <java-symbol type="dimen" name="notification_action_disabled_container_alpha" />
   <java-symbol type="id" name="tag_margin_end_when_icon_visible" />
   <java-symbol type="id" name="tag_margin_end_when_icon_gone" />
   <java-symbol type="id" name="tag_uses_right_icon_drawable" />
@@ -3316,6 +3320,7 @@
   <java-symbol type="string" name="unsupported_display_size_message" />
 
   <java-symbol type="layout" name="notification_material_action_emphasized" />
+  <java-symbol type="layout" name="notification_material_action_emphasized_tombstone" />
 
   <!-- Package name for the device provisioning package -->
   <java-symbol type="string" name="config_deviceProvisioningPackage" />
@@ -3729,6 +3734,7 @@
   <java-symbol type="string" name="config_defaultAppPredictionService" />
   <java-symbol type="string" name="config_defaultContentSuggestionsService" />
   <java-symbol type="string" name="config_defaultCredentialManagerHybridService" />
+  <java-symbol type="string" name="config_defaultCredentialProviderService" />
   <java-symbol type="string" name="config_defaultSearchUiService" />
   <java-symbol type="string" name="config_defaultSmartspaceService" />
   <java-symbol type="string" name="config_defaultWallpaperEffectsGenerationService" />
diff --git a/core/tests/coretests/src/android/credentials/OWNERS b/core/tests/coretests/src/android/credentials/OWNERS
new file mode 100644
index 0000000..00b5e06
--- /dev/null
+++ b/core/tests/coretests/src/android/credentials/OWNERS
@@ -0,0 +1,2 @@
+include /core/java/android/credentials/OWNERS
+
diff --git a/core/tests/coretests/src/android/widget/EditTextCursorAnchorInfoTest.java b/core/tests/coretests/src/android/widget/EditTextCursorAnchorInfoTest.java
new file mode 100644
index 0000000..1a01987
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/EditTextCursorAnchorInfoTest.java
@@ -0,0 +1,576 @@
+/*
+ * 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 android.widget;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Typeface;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.ShapeDrawable;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.View;
+import android.view.inputmethod.CursorAnchorInfo;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.rule.ActivityTestRule;
+
+import com.google.common.collect.ImmutableList;
+
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class EditTextCursorAnchorInfoTest {
+    private static final CursorAnchorInfo.Builder sCursorAnchorInfoBuilder =
+            new CursorAnchorInfo.Builder();
+    private static final Matrix sMatrix = new Matrix();
+    private static final int[] sLocationOnScreen = new int[2];
+    private static Typeface sTypeface;
+    private static final float TEXT_SIZE = 1f;
+    // The line height of the test font font is 1.2 * textSize.
+    private static final int LINE_HEIGHT = 12;
+    private static final CharSequence DEFAULT_TEXT = "X\nXX\nXXX\nXXXX\nXXXXX";
+    private static final ImmutableList<RectF> DEFAULT_LINE_BOUNDS = ImmutableList.of(
+            new RectF(0f, 0f, 10f, LINE_HEIGHT),
+            new RectF(0f, LINE_HEIGHT, 20f, 2 * LINE_HEIGHT),
+            new RectF(0f, 2 * LINE_HEIGHT, 30f, 3 * LINE_HEIGHT),
+            new RectF(0f, 3 * LINE_HEIGHT, 40f, 4 * LINE_HEIGHT),
+            new RectF(0f, 4 * LINE_HEIGHT, 50f, 5 * LINE_HEIGHT));
+
+    @Rule
+    public ActivityTestRule<TextViewActivity> mActivityRule = new ActivityTestRule<>(
+            TextViewActivity.class);
+    private Activity mActivity;
+    private TextView mEditText;
+
+    @BeforeClass
+    public static void setupClass() {
+        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+
+        // The test font has following coverage and width.
+        // U+0020: 10em
+        // U+002E (.): 10em
+        // U+0043 (C): 100em
+        // U+0049 (I): 1em
+        // U+004C (L): 50em
+        // U+0056 (V): 5em
+        // U+0058 (X): 10em
+        // U+005F (_): 0em
+        // U+05D0    : 1em  // HEBREW LETTER ALEF
+        // U+05D1    : 5em  // HEBREW LETTER BET
+        // U+FFFD (invalid surrogate will be replaced to this): 7em
+        // U+10331 (\uD800\uDF31): 10em
+        // Undefined : 0.5em
+        sTypeface = Typeface.createFromAsset(instrumentation.getTargetContext().getAssets(),
+                "fonts/StaticLayoutLineBreakingTestFont.ttf");
+    }
+
+    @Before
+    public void setup() {
+        mActivity = mActivityRule.getActivity();
+    }
+
+    @Test
+    public void testMatrix() {
+        setupEditText("", /* height= */ 100);
+        CursorAnchorInfo cursorAnchorInfo =
+                mEditText.getCursorAnchorInfo(0, sCursorAnchorInfoBuilder, sMatrix);
+
+        Matrix actualMatrix = cursorAnchorInfo.getMatrix();
+        Matrix expectedMatrix = new Matrix();
+        expectedMatrix.setTranslate(sLocationOnScreen[0], sLocationOnScreen[1]);
+
+        assertThat(actualMatrix).isEqualTo(expectedMatrix);
+    }
+
+    @Test
+    public void testMatrix_withTranslation() {
+        float translationX = 10f;
+        float translationY = 20f;
+        createEditText("");
+        mEditText.setTranslationX(translationX);
+        mEditText.setTranslationY(translationY);
+        measureEditText(100);
+
+        CursorAnchorInfo cursorAnchorInfo =
+                mEditText.getCursorAnchorInfo(0, sCursorAnchorInfoBuilder, sMatrix);
+
+        Matrix actualMatrix = cursorAnchorInfo.getMatrix();
+        Matrix expectedMatrix = new Matrix();
+        expectedMatrix.setTranslate(sLocationOnScreen[0] + translationX,
+                sLocationOnScreen[1] + translationY);
+
+        assertThat(actualMatrix).isEqualTo(expectedMatrix);
+    }
+
+    @Test
+    public void testVisibleLineBounds_allVisible() {
+        setupEditText(DEFAULT_TEXT, /* height= */ 100);
+        CursorAnchorInfo cursorAnchorInfo =
+                mEditText.getCursorAnchorInfo(0, sCursorAnchorInfoBuilder, sMatrix);
+
+        List<RectF> lineBounds = cursorAnchorInfo.getVisibleLineBounds();
+
+        assertThat(lineBounds).isEqualTo(DEFAULT_LINE_BOUNDS);
+    }
+
+    @Test
+    public void testVisibleLineBounds_allVisible_withLineSpacing() {
+        float lineSpacing = 10f;
+        setupEditText("X\nXX\nXXX", /* height= */ 100, lineSpacing,
+                /* lineMultiplier=*/ 1f);
+        CursorAnchorInfo cursorAnchorInfo =
+                mEditText.getCursorAnchorInfo(0, sCursorAnchorInfoBuilder, sMatrix);
+
+        List<RectF> lineBounds = cursorAnchorInfo.getVisibleLineBounds();
+
+        assertThat(lineBounds.size()).isEqualTo(3);
+        assertThat(lineBounds.get(0)).isEqualTo(new RectF(0f, 0f, 10f, LINE_HEIGHT));
+
+        float line1Top = LINE_HEIGHT + lineSpacing;
+        float line1Bottom = line1Top + LINE_HEIGHT;
+        assertThat(lineBounds.get(1)).isEqualTo(new RectF(0f, line1Top, 20f, line1Bottom));
+
+        float line2Top = 2 * (LINE_HEIGHT + lineSpacing);
+        float line2Bottom = line2Top + LINE_HEIGHT;
+        assertThat(lineBounds.get(2)).isEqualTo(new RectF(0f, line2Top, 30f, line2Bottom));
+    }
+
+    @Test
+    public void testVisibleLineBounds_allVisible_withLineMultiplier() {
+        float lineMultiplier = 2f;
+        setupEditText("X\nXX\nXXX", /* height= */ 100, /* lineSpacing= */ 0f,
+                /* lineMultiplier=*/ lineMultiplier);
+        CursorAnchorInfo cursorAnchorInfo =
+                mEditText.getCursorAnchorInfo(0, sCursorAnchorInfoBuilder, sMatrix);
+
+        List<RectF> lineBounds = cursorAnchorInfo.getVisibleLineBounds();
+
+        assertThat(lineBounds.size()).isEqualTo(3);
+        assertThat(lineBounds.get(0)).isEqualTo(new RectF(0f, 0f, 10f, LINE_HEIGHT));
+
+        float line1Top = LINE_HEIGHT * lineMultiplier;
+        float line1Bottom = line1Top + LINE_HEIGHT;
+        assertThat(lineBounds.get(1)).isEqualTo(new RectF(0f, line1Top, 20f, line1Bottom));
+
+        float line2Top = 2 * LINE_HEIGHT * lineMultiplier;
+        float line2Bottom = line2Top + LINE_HEIGHT;
+        assertThat(lineBounds.get(2)).isEqualTo(new RectF(0f, line2Top, 30f, line2Bottom));
+    }
+
+    @Test
+    public void testVisibleLineBounds_cutBottomLines() {
+        // Line top is inclusive and line bottom is exclusive. And if the visible area's
+        // bottom equals to the line top, this line is still visible. So the line height is
+        // 3 * LINE_HEIGHT - 1 to avoid including the line 3.
+        setupEditText(DEFAULT_TEXT, /* height= */ 3 * LINE_HEIGHT - 1);
+        CursorAnchorInfo cursorAnchorInfo =
+                mEditText.getCursorAnchorInfo(0, sCursorAnchorInfoBuilder, sMatrix);
+
+        List<RectF> lineBounds = cursorAnchorInfo.getVisibleLineBounds();
+
+        assertThat(lineBounds).isEqualTo(DEFAULT_LINE_BOUNDS.subList(0, 3));
+    }
+
+    @Test
+    public void testVisibleLineBounds_scrolled_cutTopLines() {
+        // First 2 lines are cut.
+        int scrollY = 2 * LINE_HEIGHT;
+        setupEditText(/* height= */ 3 * LINE_HEIGHT,
+                /* scrollY= */ scrollY);
+        CursorAnchorInfo cursorAnchorInfo =
+                mEditText.getCursorAnchorInfo(0, sCursorAnchorInfoBuilder, sMatrix);
+
+        List<RectF> lineBounds = cursorAnchorInfo.getVisibleLineBounds();
+
+        List<RectF> expectedLineBounds = subList(DEFAULT_LINE_BOUNDS, 2, 5);
+        expectedLineBounds.forEach(rectF -> rectF.offset(0, -scrollY));
+
+        assertThat(lineBounds).isEqualTo(expectedLineBounds);
+    }
+
+    @Test
+    public void testVisibleLineBounds_scrolled_cutTopAndBottomLines() {
+        // Line top is inclusive and line bottom is exclusive. And if the visible area's
+        // bottom equals to the line top, this line is still visible. So the line height is
+        // 2 * LINE_HEIGHT - 1 which only shows 2 lines.
+        int scrollY = 2 * LINE_HEIGHT;
+        setupEditText(/* height= */ 2 * LINE_HEIGHT - 1,
+                /* scrollY= */ scrollY);
+        CursorAnchorInfo cursorAnchorInfo =
+                mEditText.getCursorAnchorInfo(0, sCursorAnchorInfoBuilder, sMatrix);
+
+        List<RectF> lineBounds = cursorAnchorInfo.getVisibleLineBounds();
+
+        List<RectF> expectedLineBounds = subList(DEFAULT_LINE_BOUNDS, 2, 4);
+        expectedLineBounds.forEach(rectF -> rectF.offset(0, -scrollY));
+
+        assertThat(lineBounds).isEqualTo(expectedLineBounds);
+    }
+
+    @Test
+    public void testVisibleLineBounds_scrolled_partiallyVisibleLines() {
+        // The first 2 lines are completely cut, line 2 and 3 are partially visible.
+        int scrollY = 2 * LINE_HEIGHT + LINE_HEIGHT / 2;
+        setupEditText(/* height= */ LINE_HEIGHT,
+                /* scrollY= */ scrollY);
+        CursorAnchorInfo cursorAnchorInfo =
+                mEditText.getCursorAnchorInfo(0, sCursorAnchorInfoBuilder, sMatrix);
+
+        List<RectF> lineBounds = cursorAnchorInfo.getVisibleLineBounds();
+
+        List<RectF> expectedLineBounds = subList(DEFAULT_LINE_BOUNDS, 2, 4);
+        expectedLineBounds.forEach(rectF -> rectF.offset(0f, -scrollY));
+
+        assertThat(lineBounds).isEqualTo(expectedLineBounds);
+    }
+
+    @Test
+    public void testVisibleLineBounds_withCompoundDrawable_allVisible() {
+        int topDrawableHeight = LINE_HEIGHT;
+        Drawable topDrawable = createDrawable(topDrawableHeight);
+        Drawable bottomDrawable = createDrawable(2 * LINE_HEIGHT);
+        setupEditText(/* height= */ 100,
+                /* scrollY= */ 0, topDrawable, bottomDrawable);
+        CursorAnchorInfo cursorAnchorInfo =
+                mEditText.getCursorAnchorInfo(0, sCursorAnchorInfoBuilder, sMatrix);
+
+        List<RectF> lineBounds = cursorAnchorInfo.getVisibleLineBounds();
+
+        List<RectF> expectedLineBounds = copy(DEFAULT_LINE_BOUNDS);
+        expectedLineBounds.forEach(rectF -> rectF.offset(0f, topDrawableHeight));
+
+        assertThat(lineBounds).isEqualTo(expectedLineBounds);
+    }
+
+    @Test
+    public void testVisibleLineBounds_withCompoundDrawable_cutBottomLines() {
+        // The view's totally height is 5 * LINE_HEIGHT, and drawables take 3 * LINE_HEIGHT.
+        // Only first 2 lines are visible.
+        int topDrawableHeight = LINE_HEIGHT;
+        Drawable topDrawable = createDrawable(topDrawableHeight);
+        Drawable bottomDrawable = createDrawable(2 * LINE_HEIGHT + 1);
+        setupEditText(/* height= */ 5 * LINE_HEIGHT,
+                /* scrollY= */ 0, topDrawable, bottomDrawable);
+        CursorAnchorInfo cursorAnchorInfo =
+                mEditText.getCursorAnchorInfo(0, sCursorAnchorInfoBuilder, sMatrix);
+
+        List<RectF> lineBounds = cursorAnchorInfo.getVisibleLineBounds();
+
+        List<RectF> expectedLineBounds = subList(DEFAULT_LINE_BOUNDS, 0, 2);
+        expectedLineBounds.forEach(rectF -> rectF.offset(0f, topDrawableHeight));
+
+        assertThat(lineBounds).isEqualTo(expectedLineBounds);
+    }
+
+    @Test
+    public void testVisibleLineBounds_withCompoundDrawable_scrolled() {
+        // The view's totally height is 5 * LINE_HEIGHT, and drawables take 3 * LINE_HEIGHT.
+        // So 2 lines are visible. Because the view is scrolled vertically by LINE_HEIGHT,
+        // the line 1 and 2 are visible.
+        int topDrawableHeight = LINE_HEIGHT;
+        Drawable topDrawable = createDrawable(topDrawableHeight);
+        Drawable bottomDrawable = createDrawable(2 * LINE_HEIGHT + 1);
+        int scrollY = LINE_HEIGHT;
+        setupEditText(/* height= */ 5 * LINE_HEIGHT, scrollY,
+                topDrawable, bottomDrawable);
+        CursorAnchorInfo cursorAnchorInfo =
+                mEditText.getCursorAnchorInfo(0, sCursorAnchorInfoBuilder, sMatrix);
+
+        List<RectF> lineBounds = cursorAnchorInfo.getVisibleLineBounds();
+
+        List<RectF> expectedLineBounds = subList(DEFAULT_LINE_BOUNDS, 1, 3);
+        expectedLineBounds.forEach(rectF -> rectF.offset(0f, topDrawableHeight - scrollY));
+
+        assertThat(lineBounds).isEqualTo(expectedLineBounds);
+    }
+
+    @Test
+    public void testVisibleLineBounds_withCompoundDrawable_partiallyVisible() {
+        // The view's totally height is 5 * LINE_HEIGHT, and drawables take 3 * LINE_HEIGHT.
+        // And because the view is scrolled vertically by 0.5 * LINE_HEIGHT,
+        // the line 0, 1 and 2 are visible.
+        int topDrawableHeight = LINE_HEIGHT;
+        Drawable topDrawable = createDrawable(topDrawableHeight);
+        Drawable bottomDrawable = createDrawable(2 * LINE_HEIGHT + 1);
+        int scrollY = LINE_HEIGHT / 2;
+        setupEditText(/* height= */ 5 * LINE_HEIGHT, scrollY,
+                topDrawable, bottomDrawable);
+        CursorAnchorInfo cursorAnchorInfo =
+                mEditText.getCursorAnchorInfo(0, sCursorAnchorInfoBuilder, sMatrix);
+
+        List<RectF> lineBounds = cursorAnchorInfo.getVisibleLineBounds();
+
+        List<RectF> expectedLineBounds = subList(DEFAULT_LINE_BOUNDS, 0, 3);
+        expectedLineBounds.forEach(rectF -> rectF.offset(0f, topDrawableHeight - scrollY));
+
+        assertThat(lineBounds).isEqualTo(expectedLineBounds);
+    }
+
+    @Test
+    public void testVisibleLineBounds_withPaddings_allVisible() {
+        int topPadding = LINE_HEIGHT;
+        int bottomPadding = LINE_HEIGHT;
+        setupEditText(/* height= */ 100, /* scrollY= */ 0, topPadding, bottomPadding);
+        CursorAnchorInfo cursorAnchorInfo =
+                mEditText.getCursorAnchorInfo(0, sCursorAnchorInfoBuilder, sMatrix);
+
+        List<RectF> lineBounds = cursorAnchorInfo.getVisibleLineBounds();
+
+        List<RectF> expectedLineBounds = copy(DEFAULT_LINE_BOUNDS);
+        expectedLineBounds.forEach(rectF -> rectF.offset(0f, topPadding));
+
+        assertThat(lineBounds).isEqualTo(expectedLineBounds);
+    }
+
+    @Test
+    public void testVisibleLineBounds_withPaddings_cutBottomLines() {
+        // The view's totally height is 5 * LINE_HEIGHT, and paddings take 3 * LINE_HEIGHT.
+        // So 2 lines are visible.
+        int topPadding = LINE_HEIGHT;
+        int bottomPadding = 2 * LINE_HEIGHT + 1;
+        setupEditText(/* height= */ 5 * LINE_HEIGHT, /* scrollY= */ 0, topPadding, bottomPadding);
+        CursorAnchorInfo cursorAnchorInfo =
+                mEditText.getCursorAnchorInfo(0, sCursorAnchorInfoBuilder, sMatrix);
+
+        List<RectF> lineBounds = cursorAnchorInfo.getVisibleLineBounds();
+
+        List<RectF> expectedLineBounds = subList(DEFAULT_LINE_BOUNDS, 0, 2);
+        expectedLineBounds.forEach(rectF -> rectF.offset(0f, topPadding));
+
+        assertThat(lineBounds).isEqualTo(expectedLineBounds);
+    }
+
+    @Test
+    public void testVisibleLineBounds_withPaddings_scrolled() {
+        // The view's totally height is 5 * LINE_HEIGHT, and paddings take 3 * LINE_HEIGHT.
+        // So 2 lines are visible. Because the view is scrolled vertically by LINE_HEIGHT,
+        // the line 1 and 2 are visible.
+        int topPadding = LINE_HEIGHT;
+        int bottomPadding = 2 * LINE_HEIGHT + 1;
+        int scrollY = LINE_HEIGHT;
+        setupEditText(/* height= */ 5 * LINE_HEIGHT, scrollY,
+                topPadding, bottomPadding);
+        CursorAnchorInfo cursorAnchorInfo =
+                mEditText.getCursorAnchorInfo(0, sCursorAnchorInfoBuilder, sMatrix);
+
+        List<RectF> lineBounds = cursorAnchorInfo.getVisibleLineBounds();
+
+        List<RectF> expectedLineBounds = subList(DEFAULT_LINE_BOUNDS, 1, 3);
+        expectedLineBounds.forEach(rectF -> rectF.offset(0f, topPadding - scrollY));
+
+        assertThat(lineBounds).isEqualTo(expectedLineBounds);
+    }
+
+    @Test
+    public void testVisibleLineBounds_withPadding_partiallyVisible() {
+        // The view's totally height is 5 * LINE_HEIGHT, and paddings take 3 * LINE_HEIGHT.
+        // And because the view is scrolled vertically by 0.5 * LINE_HEIGHT, the line 0, 1 and 2
+        // are visible.
+        int topPadding = LINE_HEIGHT;
+        int bottomPadding = 2 * LINE_HEIGHT + 1;
+        int scrollY = LINE_HEIGHT / 2;
+        setupEditText(/* height= */ 5 * LINE_HEIGHT, scrollY,
+                topPadding, bottomPadding);
+        CursorAnchorInfo cursorAnchorInfo =
+                mEditText.getCursorAnchorInfo(0, sCursorAnchorInfoBuilder, sMatrix);
+
+        List<RectF> lineBounds = cursorAnchorInfo.getVisibleLineBounds();
+
+        List<RectF> expectedLineBounds = subList(DEFAULT_LINE_BOUNDS, 0, 3);
+        expectedLineBounds.forEach(rectF -> rectF.offset(0f, topPadding - scrollY));
+
+        assertThat(lineBounds).isEqualTo(expectedLineBounds);
+    }
+
+    @Test
+    public void testVisibleLineBounds_clippedTop() {
+        // The first line is clipped off.
+        setupVerticalClippedEditText(LINE_HEIGHT, 5 * LINE_HEIGHT);
+        CursorAnchorInfo cursorAnchorInfo =
+                mEditText.getCursorAnchorInfo(0, sCursorAnchorInfoBuilder, sMatrix);
+
+        List<RectF> lineBounds = cursorAnchorInfo.getVisibleLineBounds();
+
+        List<RectF> expectedLineBounds = subList(DEFAULT_LINE_BOUNDS, 1, 5);
+        assertThat(lineBounds).isEqualTo(expectedLineBounds);
+    }
+
+    @Test
+    public void testVisibleLineBounds_clippedBottom() {
+        // The last line is clipped off.
+        setupVerticalClippedEditText(0, 4 * LINE_HEIGHT - 1);
+        CursorAnchorInfo cursorAnchorInfo =
+                mEditText.getCursorAnchorInfo(0, sCursorAnchorInfoBuilder, sMatrix);
+
+        List<RectF> lineBounds = cursorAnchorInfo.getVisibleLineBounds();
+
+        List<RectF> expectedLineBounds = subList(DEFAULT_LINE_BOUNDS, 0, 4);
+        assertThat(lineBounds).isEqualTo(expectedLineBounds);
+    }
+
+    @Test
+    public void testVisibleLineBounds_clippedTopAndBottom() {
+        // The first and last line are clipped off.
+        setupVerticalClippedEditText(LINE_HEIGHT, 4 * LINE_HEIGHT - 1);
+        CursorAnchorInfo cursorAnchorInfo =
+                mEditText.getCursorAnchorInfo(0, sCursorAnchorInfoBuilder, sMatrix);
+
+        List<RectF> lineBounds = cursorAnchorInfo.getVisibleLineBounds();
+
+        List<RectF> expectedLineBounds = subList(DEFAULT_LINE_BOUNDS, 1, 4);
+        assertThat(lineBounds).isEqualTo(expectedLineBounds);
+    }
+
+    private List<RectF> copy(List<RectF> rectFList) {
+        List<RectF> result = new ArrayList<>();
+        for (RectF rectF : rectFList) {
+            result.add(new RectF(rectF));
+        }
+        return result;
+    }
+    private List<RectF> subList(List<RectF> rectFList, int start, int end) {
+        List<RectF> result = new ArrayList<>();
+        for (int index = start; index < end; ++index) {
+            result.add(new RectF(rectFList.get(index)));
+        }
+        return result;
+    }
+
+    private void setupVerticalClippedEditText(int visibleTop, int visibleBottom) {
+        ScrollView scrollView = new ScrollView(mActivity);
+        mEditText = new EditText(mActivity);
+        mEditText.setTypeface(sTypeface);
+        mEditText.setText(DEFAULT_TEXT);
+        mEditText.setTextSize(TypedValue.COMPLEX_UNIT_PX, TEXT_SIZE);
+
+        mEditText.setPadding(0, 0, 0, 0);
+        mEditText.setCompoundDrawables(null, null, null, null);
+        mEditText.setCompoundDrawablePadding(0);
+
+        mEditText.scrollTo(0, 0);
+        mEditText.setLineSpacing(0f, 1f);
+
+        // Place the text layout top to the view's top.
+        mEditText.setGravity(Gravity.TOP);
+        int width = 1000;
+        int height = visibleBottom - visibleTop;
+
+        scrollView.addView(mEditText, new FrameLayout.LayoutParams(
+                View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
+                View.MeasureSpec.makeMeasureSpec(5 * LINE_HEIGHT, View.MeasureSpec.EXACTLY)));
+        scrollView.measure(
+                View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
+                View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY));
+        scrollView.layout(0, 0, width, height);
+
+        scrollView.scrollTo(0, visibleTop);
+    }
+
+    private void setupEditText(CharSequence text, int height) {
+        createEditText(text);
+        measureEditText(height);
+    }
+
+    private void setupEditText(CharSequence text, int height, float lineSpacing,
+            float lineMultiplier) {
+        createEditText(text);
+        mEditText.setLineSpacing(lineSpacing, lineMultiplier);
+        measureEditText(height);
+    }
+
+    private void setupEditText(int height, int scrollY) {
+        createEditText();
+        mEditText.scrollTo(0, scrollY);
+        measureEditText(height);
+    }
+
+    private void setupEditText(int height, int scrollY, Drawable drawableTop,
+            Drawable drawableBottom) {
+        createEditText();
+        mEditText.scrollTo(0, scrollY);
+        mEditText.setCompoundDrawables(null, drawableTop, null, drawableBottom);
+        measureEditText(height);
+    }
+
+    private void setupEditText(int height, int scrollY, int paddingTop,
+            int paddingBottom) {
+        createEditText();
+        mEditText.scrollTo(0, scrollY);
+        mEditText.setPadding(0, paddingTop, 0, paddingBottom);
+        measureEditText(height);
+    }
+
+    private void createEditText() {
+        createEditText(DEFAULT_TEXT);
+    }
+
+    private void createEditText(CharSequence text) {
+        mEditText = new EditText(mActivity);
+        mEditText.setTypeface(sTypeface);
+        mEditText.setText(text);
+        mEditText.setTextSize(TypedValue.COMPLEX_UNIT_PX, TEXT_SIZE);
+
+        mEditText.setPadding(0, 0, 0, 0);
+        mEditText.setCompoundDrawables(null, null, null, null);
+        mEditText.setCompoundDrawablePadding(0);
+
+        mEditText.scrollTo(0, 0);
+        mEditText.setLineSpacing(0f, 1f);
+
+        // Place the text layout top to the view's top.
+        mEditText.setGravity(Gravity.TOP);
+    }
+
+    private void measureEditText(int height) {
+        // width equals to 1000 is enough to avoid line break for all test cases.
+        measureEditText(1000, height);
+    }
+
+    private void measureEditText(int width, int height) {
+        mEditText.measure(
+                View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
+                View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY));
+        mEditText.layout(0, 0, width, height);
+
+        mEditText.getLocationOnScreen(sLocationOnScreen);
+    }
+
+    private Drawable createDrawable(int height) {
+        // width is not important for this drawable, make it 1 pixel.
+        return createDrawable(1, height);
+    }
+
+    private Drawable createDrawable(int width, int height) {
+        ShapeDrawable drawable = new ShapeDrawable();
+        drawable.setBounds(new Rect(0, 0, width, height));
+        return drawable;
+    }
+}
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityUtilsTest.java b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityUtilsTest.java
index 045b3a2..3ea7f47 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityUtilsTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityUtilsTest.java
@@ -16,8 +16,19 @@
 
 package com.android.internal.accessibility;
 
-import static junit.framework.Assert.assertEquals;
+import static com.android.internal.accessibility.util.AccessibilityUtils.ACCESSIBILITY_MENU_IN_SYSTEM;
+import static com.android.internal.accessibility.util.AccessibilityUtils.MENU_SERVICE_RELATIVE_CLASS_NAME;
 
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
 import android.text.ParcelableSpan;
 import android.text.SpannableString;
 import android.text.style.LocaleSpan;
@@ -26,9 +37,13 @@
 
 import com.android.internal.accessibility.util.AccessibilityUtils;
 
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
+import java.util.List;
 import java.util.Locale;
 
 /**
@@ -36,6 +51,15 @@
  */
 @RunWith(AndroidJUnit4.class)
 public class AccessibilityUtilsTest {
+    private static final int USER_ID = 123;
+    @Mock
+    private PackageManager mMockPackageManager;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+    }
+
     @Test
     public void textOrSpanChanged_stringChange_returnTextChange() {
         final CharSequence beforeText = "a";
@@ -44,7 +68,7 @@
 
         @AccessibilityUtils.A11yTextChangeType int type = AccessibilityUtils.textOrSpanChanged(
                 beforeText, afterText);
-        assertEquals(AccessibilityUtils.TEXT, type);
+        assertThat(type).isEqualTo(AccessibilityUtils.TEXT);
     }
 
     @Test
@@ -55,7 +79,7 @@
 
         @AccessibilityUtils.A11yTextChangeType int type = AccessibilityUtils.textOrSpanChanged(
                 beforeText, afterText);
-        assertEquals(AccessibilityUtils.NONE, type);
+        assertThat(type).isEqualTo(AccessibilityUtils.NONE);
     }
 
     @Test
@@ -68,7 +92,7 @@
 
         @AccessibilityUtils.A11yTextChangeType int type = AccessibilityUtils.textOrSpanChanged(
                 beforeText, afterText);
-        assertEquals(AccessibilityUtils.NONE, type);
+        assertThat(type).isEqualTo(AccessibilityUtils.NONE);
     }
 
     @Test
@@ -81,7 +105,7 @@
 
         @AccessibilityUtils.A11yTextChangeType int type = AccessibilityUtils.textOrSpanChanged(
                 beforeText, afterText);
-        assertEquals(AccessibilityUtils.PARCELABLE_SPAN, type);
+        assertThat(type).isEqualTo(AccessibilityUtils.PARCELABLE_SPAN);
     }
 
     @Test
@@ -96,7 +120,7 @@
 
         @AccessibilityUtils.A11yTextChangeType int type = AccessibilityUtils.textOrSpanChanged(
                 beforeText, afterText);
-        assertEquals(AccessibilityUtils.PARCELABLE_SPAN, type);
+        assertThat(type).isEqualTo(AccessibilityUtils.PARCELABLE_SPAN);
     }
 
     @Test
@@ -110,7 +134,7 @@
 
         @AccessibilityUtils.A11yTextChangeType int type = AccessibilityUtils.textOrSpanChanged(
                 beforeText, afterText);
-        assertEquals(AccessibilityUtils.NONE, type);
+        assertThat(type).isEqualTo(AccessibilityUtils.NONE);
     }
 
     @Test
@@ -124,6 +148,72 @@
 
         @AccessibilityUtils.A11yTextChangeType int type = AccessibilityUtils.textOrSpanChanged(
                 beforeText, afterText);
-        assertEquals(AccessibilityUtils.PARCELABLE_SPAN, type);
+        assertThat(type).isEqualTo(AccessibilityUtils.PARCELABLE_SPAN);
+    }
+
+    @Test
+    public void getAccessibilityMenuComponentToMigrate_isNull_whenNoMenuComponents() {
+        when(mMockPackageManager.queryIntentServicesAsUser(any(), any(),
+                eq(USER_ID))).thenReturn(List.of());
+
+        final ComponentName result = AccessibilityUtils.getAccessibilityMenuComponentToMigrate(
+                mMockPackageManager, USER_ID);
+
+        assertThat(result).isNull();
+    }
+
+    @Test
+    public void getAccessibilityMenuComponentToMigrate_isNull_whenTooManyMenuComponents() {
+        when(mMockPackageManager.queryIntentServicesAsUser(any(), any(),
+                eq(USER_ID))).thenReturn(List.of(
+                createResolveInfo(ComponentName.createRelative("external1",
+                        MENU_SERVICE_RELATIVE_CLASS_NAME)),
+                createResolveInfo(ComponentName.createRelative("external2",
+                        MENU_SERVICE_RELATIVE_CLASS_NAME)),
+                createResolveInfo(ComponentName.createRelative("external3",
+                        MENU_SERVICE_RELATIVE_CLASS_NAME))));
+
+        final ComponentName result = AccessibilityUtils.getAccessibilityMenuComponentToMigrate(
+                mMockPackageManager, USER_ID);
+
+        assertThat(result).isNull();
+    }
+
+    @Test
+    public void getAccessibilityMenuComponentToMigrate_isNull_whenMenuInSystemNotFound() {
+        when(mMockPackageManager.queryIntentServicesAsUser(any(), any(),
+                eq(USER_ID))).thenReturn(List.of(
+                createResolveInfo(ComponentName.createRelative("external1",
+                        MENU_SERVICE_RELATIVE_CLASS_NAME)),
+                createResolveInfo(ComponentName.createRelative("external2",
+                        MENU_SERVICE_RELATIVE_CLASS_NAME))));
+
+        final ComponentName result = AccessibilityUtils.getAccessibilityMenuComponentToMigrate(
+                mMockPackageManager, USER_ID);
+
+        assertThat(result).isNull();
+    }
+
+    @Test
+    public void getAccessibilityMenuComponentToMigrate_returnsMenuOutsideSystem() {
+        ComponentName menuOutsideSystem = ComponentName.createRelative("external1",
+                MENU_SERVICE_RELATIVE_CLASS_NAME);
+        when(mMockPackageManager.queryIntentServicesAsUser(any(), any(),
+                eq(USER_ID))).thenReturn(List.of(
+                createResolveInfo(menuOutsideSystem),
+                createResolveInfo(ACCESSIBILITY_MENU_IN_SYSTEM)));
+
+        final ComponentName result = AccessibilityUtils.getAccessibilityMenuComponentToMigrate(
+                mMockPackageManager, USER_ID);
+
+        assertThat(result).isEqualTo(menuOutsideSystem);
+    }
+
+    private static ResolveInfo createResolveInfo(ComponentName componentName) {
+        ResolveInfo resolveInfo = new ResolveInfo();
+        resolveInfo.serviceInfo = new ServiceInfo();
+        resolveInfo.serviceInfo.packageName = componentName.getPackageName();
+        resolveInfo.serviceInfo.name = componentName.getClassName();
+        return resolveInfo;
     }
 }
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 53f4747..0d74e12 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -4417,6 +4417,12 @@
       "group": "WM_DEBUG_TASKS",
       "at": "com\/android\/server\/wm\/RootWindowContainer.java"
     },
+    "2043434284": {
+      "message": "setWallpaperShowWhenLocked: non-existent wallpaper token: %s",
+      "level": "WARN",
+      "group": "WM_ERROR",
+      "at": "com\/android\/server\/wm\/WindowManagerService.java"
+    },
     "2045641491": {
       "message": "Checking %d opening apps (frozen=%b timeout=%b)...",
       "level": "VERBOSE",
diff --git a/data/keyboards/Vendor_18d1_Product_9451.idc b/data/keyboards/Vendor_18d1_Product_9451.idc
new file mode 100644
index 0000000..e13fcb2
--- /dev/null
+++ b/data/keyboards/Vendor_18d1_Product_9451.idc
@@ -0,0 +1,23 @@
+# 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.
+
+#
+# Input Device Configuration file for a flavor of the Google Reference RCU Remote.
+#
+#
+
+# Basic Parameters
+keyboard.layout = Vendor_0957_Product_0001
+keyboard.doNotWakeByDefault = 1
+audio.mic = 1
\ No newline at end of file
diff --git a/graphics/java/android/graphics/MeshSpecification.java b/graphics/java/android/graphics/MeshSpecification.java
index 52b4002..6b5f1da 100644
--- a/graphics/java/android/graphics/MeshSpecification.java
+++ b/graphics/java/android/graphics/MeshSpecification.java
@@ -17,11 +17,15 @@
 package android.graphics;
 
 import android.annotation.IntDef;
+import android.annotation.IntRange;
 import android.annotation.NonNull;
+import android.annotation.Size;
+import android.annotation.SuppressLint;
 
 import libcore.util.NativeAllocationRegistry;
 
-import java.util.List;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 
 /**
  * Class responsible for holding specifications for {@link Mesh} creations. This class
@@ -43,100 +47,162 @@
     long mNativeMeshSpec;
 
     /**
-     * Constants for {@link #make(List, int, List, String, String)}
+     * Constants for {@link #make(Attribute[], int, Varying[], String, String)}
      * to determine alpha type. Describes how to interpret the alpha component of a pixel.
+     *
+     * @hide
      */
-    @IntDef({UNKNOWN, OPAQUE, PREMUL, UNPREMULT})
+    @IntDef({ALPHA_TYPE_UNKNOWN, ALPHA_TYPE_OPAQUE, ALPHA_TYPE_PREMUL, ALPHA_TYPE_PREMULT})
+    @Retention(RetentionPolicy.SOURCE)
     private @interface AlphaType {}
 
     /**
      * uninitialized.
      */
-    public static final int UNKNOWN = 0;
+    public static final int ALPHA_TYPE_UNKNOWN = 0;
 
     /**
      * Pixel is opaque.
      */
-    public static final int OPAQUE = 1;
+    public static final int ALPHA_TYPE_OPAQUE = 1;
 
     /**
      * Pixel components are premultiplied by alpha.
      */
-    public static final int PREMUL = 2;
+    public static final int ALPHA_TYPE_PREMUL = 2;
 
     /**
      * Pixel components are independent of alpha.
      */
-    public static final int UNPREMULT = 3;
+    public static final int ALPHA_TYPE_PREMULT = 3;
 
     /**
      * Constants for {@link Attribute} and {@link Varying} for determining the data type.
+     *
+     * @hide
      */
-    @IntDef({FLOAT, FLOAT2, FLOAT3, FLOAT4, UBYTE4})
+    @IntDef({TYPE_FLOAT, TYPE_FLOAT2, TYPE_FLOAT3, TYPE_FLOAT4, TYPE_UBYTE4})
+    @Retention(RetentionPolicy.SOURCE)
     private @interface Type {}
 
     /**
      * Represents one float. Its equivalent shader type is float.
      */
-    public static final int FLOAT = 0;
+    public static final int TYPE_FLOAT = 0;
 
     /**
      * Represents two floats. Its equivalent shader type is float2.
      */
-    public static final int FLOAT2 = 1;
+    public static final int TYPE_FLOAT2 = 1;
 
     /**
      * Represents three floats. Its equivalent shader type is float3.
      */
-    public static final int FLOAT3 = 2;
+    public static final int TYPE_FLOAT3 = 2;
 
     /**
      * Represents four floats. Its equivalent shader type is float4.
      */
-    public static final int FLOAT4 = 3;
+    public static final int TYPE_FLOAT4 = 3;
 
     /**
      * Represents four bytes. Its equivalent shader type is half4.
      */
-    public static final int UBYTE4 = 4;
+    public static final int TYPE_UBYTE4 = 4;
 
     /**
-     * Data class to represent a single attribute in a shader. Note that type parameter must be
-     * one of {@link #FLOAT}, {@link #FLOAT2}, {@link #FLOAT3}, {@link #FLOAT4}, or {@link #UBYTE4}.
+     * Data class to represent a single attribute in a shader.
      *
      * Note that offset is the offset in number of bytes. For example, if we had two attributes
      *
+     * <pre>
      * Float3 att1
      * Float att2
+     * </pre>
      *
      * att1 would have an offset of 0, while att2 would have an offset of 12 bytes.
      */
     public static class Attribute {
         @Type
-        private int mType;
-        private int mOffset;
-        private String mName;
+        private final int mType;
+        private final int mOffset;
+        private final String mName;
 
         public Attribute(@Type int type, int offset, @NonNull String name) {
             mType = type;
             mOffset = offset;
             mName = name;
         }
+
+        /**
+         * Return the corresponding data type for this {@link Attribute}.
+         */
+        @Type
+        public int getType() {
+            return mType;
+        }
+
+        /**
+         * Return the offset of the attribute in bytes
+         */
+        public int getOffset() {
+            return mOffset;
+        }
+
+        /**
+         * Return the name of this {@link Attribute}
+         */
+        @NonNull
+        public String getName() {
+            return mName;
+        }
+
+        @Override
+        public String toString() {
+            return "Attribute{"
+                    + "mType=" + mType
+                    + ", mOffset=" + mOffset
+                    + ", mName='" + mName + '\''
+                    + '}';
+        }
     }
 
     /**
-     * Data class to represent a single varying variable. Note that type parameter must be
-     * one of {@link #FLOAT}, {@link #FLOAT2}, {@link #FLOAT3}, {@link #FLOAT4}, or {@link #UBYTE4}.
+     * Data class to represent a single varying variable.
      */
     public static class Varying {
         @Type
-        private int mType;
-        private String mName;
+        private final int mType;
+        private final String mName;
 
         public Varying(@Type int type, @NonNull String name) {
             mType = type;
             mName = name;
         }
+
+        /**
+         * Return the corresponding data type for this {@link Varying}.
+         */
+        @Type
+        public int getType() {
+            return mType;
+        }
+
+        /**
+         * Return the name of this {@link Varying}
+         */
+        @NonNull
+        public String getName() {
+            return mName;
+        }
+
+        @Override
+        public String toString() {
+            return "Varying{"
+                    + "mType=" + mType
+                    + ", mName='" + mName + '\''
+                    + '}';
+        }
     }
 
     private static class MeshSpecificationHolder {
@@ -146,7 +212,9 @@
     }
 
     /**
-     * Creates a {@link MeshSpecification} object for use within {@link Mesh}.
+     * Creates a {@link MeshSpecification} object for use within {@link Mesh}. This uses a default
+     * color space of {@link ColorSpace.Named#SRGB} and {@link AlphaType} of
+     * {@link #ALPHA_TYPE_PREMUL}.
      *
      * @param attributes     list of attributes represented by {@link Attribute}. Can hold a max of
      *                       8.
@@ -162,11 +230,14 @@
      * @return {@link MeshSpecification} object for use when creating {@link Mesh}
      */
     @NonNull
-    public static MeshSpecification make(@NonNull List<Attribute> attributes, int vertexStride,
-            @NonNull List<Varying> varyings, @NonNull String vertexShader,
+    public static MeshSpecification make(
+            @SuppressLint("ArrayReturn") @NonNull @Size(max = 8) Attribute[] attributes,
+            @IntRange(from = 1, to = 1024) int vertexStride,
+            @SuppressLint("ArrayReturn") @NonNull @Size(max = 6) Varying[] varyings,
+            @NonNull String vertexShader,
             @NonNull String fragmentShader) {
-        long nativeMeshSpec = nativeMake(attributes.toArray(new Attribute[attributes.size()]),
-                vertexStride, varyings.toArray(new Varying[varyings.size()]), vertexShader,
+        long nativeMeshSpec = nativeMake(attributes,
+                vertexStride, varyings, vertexShader,
                 fragmentShader);
         if (nativeMeshSpec == 0) {
             throw new IllegalArgumentException("MeshSpecification construction failed");
@@ -175,7 +246,8 @@
     }
 
     /**
-     * Creates a {@link MeshSpecification} object.
+     * Creates a {@link MeshSpecification} object.  This uses a default {@link AlphaType} of
+     * {@link #ALPHA_TYPE_PREMUL}.
      *
      * @param attributes     list of attributes represented by {@link Attribute}. Can hold a max of
      *                       8.
@@ -192,11 +264,16 @@
      * @return {@link MeshSpecification} object for use when creating {@link Mesh}
      */
     @NonNull
-    public static MeshSpecification make(@NonNull List<Attribute> attributes, int vertexStride,
-            @NonNull List<Varying> varyings, @NonNull String vertexShader,
-            @NonNull String fragmentShader, @NonNull ColorSpace colorSpace) {
-        long nativeMeshSpec = nativeMakeWithCS(attributes.toArray(new Attribute[attributes.size()]),
-                vertexStride, varyings.toArray(new Varying[varyings.size()]), vertexShader,
+    public static MeshSpecification make(
+            @SuppressLint("ArrayReturn") @NonNull @Size(max = 8) Attribute[] attributes,
+            @IntRange(from = 1, to = 1024) int vertexStride,
+            @SuppressLint("ArrayReturn") @NonNull @Size(max = 6) Varying[] varyings,
+            @NonNull String vertexShader,
+            @NonNull String fragmentShader,
+            @NonNull ColorSpace colorSpace
+    ) {
+        long nativeMeshSpec = nativeMakeWithCS(attributes,
+                vertexStride, varyings, vertexShader,
                 fragmentShader, colorSpace.getNativeInstance());
         if (nativeMeshSpec == 0) {
             throw new IllegalArgumentException("MeshSpecification construction failed");
@@ -221,20 +298,23 @@
      * @param colorSpace     {@link ColorSpace} to tell what color space to work in.
      * @param alphaType      Describes how to interpret the alpha component for a pixel. Must be
      *                       one of
-     *                       {@link MeshSpecification#UNKNOWN},
-     *                       {@link MeshSpecification#OPAQUE},
-     *                       {@link MeshSpecification#PREMUL}, or
-     *                       {@link MeshSpecification#UNPREMULT}
+     *                       {@link MeshSpecification#ALPHA_TYPE_UNKNOWN},
+     *                       {@link MeshSpecification#ALPHA_TYPE_OPAQUE},
+     *                       {@link MeshSpecification#ALPHA_TYPE_PREMUL}, or
+     *                       {@link MeshSpecification#ALPHA_TYPE_PREMULT}
      * @return {@link MeshSpecification} object for use when creating {@link Mesh}
      */
     @NonNull
-    public static MeshSpecification make(@NonNull List<Attribute> attributes, int vertexStride,
-            @NonNull List<Varying> varyings, @NonNull String vertexShader,
-            @NonNull String fragmentShader, @NonNull ColorSpace colorSpace,
+    public static MeshSpecification make(
+            @SuppressLint("ArrayReturn") @NonNull @Size(max = 8) Attribute[] attributes,
+            @IntRange(from = 1, to = 1024) int vertexStride,
+            @SuppressLint("ArrayReturn") @NonNull @Size(max = 6) Varying[] varyings,
+            @NonNull String vertexShader,
+            @NonNull String fragmentShader,
+            @NonNull ColorSpace colorSpace,
             @AlphaType int alphaType) {
         long nativeMeshSpec =
-                nativeMakeWithAlpha(attributes.toArray(new Attribute[attributes.size()]),
-                        vertexStride, varyings.toArray(new Varying[varyings.size()]), vertexShader,
+                nativeMakeWithAlpha(attributes, vertexStride, varyings, vertexShader,
                         fragmentShader, colorSpace.getNativeInstance(), alphaType);
         if (nativeMeshSpec == 0) {
             throw new IllegalArgumentException("MeshSpecification construction failed");
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ProtoLogController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ProtoLogController.java
index d276002..88525aa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ProtoLogController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ProtoLogController.java
@@ -84,6 +84,15 @@
                 String[] groups = Arrays.copyOfRange(args, 1, args.length);
                 return mShellProtoLog.stopTextLogging(groups, pw) == 0;
             }
+            case "save-for-bugreport": {
+                if (!mShellProtoLog.isProtoEnabled()) {
+                    pw.println("Logging to proto is not enabled for WMShell.");
+                    return false;
+                }
+                mShellProtoLog.stopProtoLog(pw, true /* writeToFile */);
+                mShellProtoLog.startProtoLog(pw);
+                return true;
+            }
             default: {
                 pw.println("Invalid command: " + args[0]);
                 printShellCommandHelp(pw, "");
@@ -108,5 +117,7 @@
         pw.println(prefix + "  Enable logcat logging for given groups.");
         pw.println(prefix + "disable-text [group...]");
         pw.println(prefix + "  Disable logcat logging for given groups.");
+        pw.println(prefix + "save-for-bugreport");
+        pw.println(prefix + "  Flush proto logging to file, only if it's enabled.");
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
index be3e0a1..ab96856 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
@@ -203,6 +203,7 @@
             TvPipMenuController tvPipMenuController,
             SyncTransactionQueue syncTransactionQueue,
             TvPipBoundsState tvPipBoundsState,
+            PipSizeSpecHandler pipSizeSpecHandler,
             PipTransitionState pipTransitionState,
             TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
             PipAnimationController pipAnimationController,
@@ -214,10 +215,11 @@
             PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer,
             @ShellMainThread ShellExecutor mainExecutor) {
         return new TvPipTaskOrganizer(context,
-                syncTransactionQueue, pipTransitionState, tvPipBoundsState, tvPipBoundsAlgorithm,
-                tvPipMenuController, pipAnimationController, pipSurfaceTransactionHelper,
-                pipTransitionController, pipParamsChangedForwarder, splitScreenControllerOptional,
-                displayController, pipUiEventLogger, shellTaskOrganizer, mainExecutor);
+                syncTransactionQueue, pipTransitionState, tvPipBoundsState, pipSizeSpecHandler,
+                tvPipBoundsAlgorithm, tvPipMenuController, pipAnimationController,
+                pipSurfaceTransactionHelper, pipTransitionController, pipParamsChangedForwarder,
+                splitScreenControllerOptional, displayController, pipUiEventLogger,
+                shellTaskOrganizer, mainExecutor);
     }
 
     @WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index d83f1eb..2cdd86f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -444,6 +444,7 @@
             SyncTransactionQueue syncTransactionQueue,
             PipTransitionState pipTransitionState,
             PipBoundsState pipBoundsState,
+            PipSizeSpecHandler pipSizeSpecHandler,
             PipBoundsAlgorithm pipBoundsAlgorithm,
             PhonePipMenuController menuPhoneController,
             PipAnimationController pipAnimationController,
@@ -455,10 +456,11 @@
             PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer,
             @ShellMainThread ShellExecutor mainExecutor) {
         return new PipTaskOrganizer(context,
-                syncTransactionQueue, pipTransitionState, pipBoundsState, pipBoundsAlgorithm,
-                menuPhoneController, pipAnimationController, pipSurfaceTransactionHelper,
-                pipTransitionController, pipParamsChangedForwarder, splitScreenControllerOptional,
-                displayController, pipUiEventLogger, shellTaskOrganizer, mainExecutor);
+                syncTransactionQueue, pipTransitionState, pipBoundsState, pipSizeSpecHandler,
+                pipBoundsAlgorithm, menuPhoneController, pipAnimationController,
+                pipSurfaceTransactionHelper, pipTransitionController, pipParamsChangedForwarder,
+                splitScreenControllerOptional, displayController, pipUiEventLogger,
+                shellTaskOrganizer, mainExecutor);
     }
 
     @WMSingleton
@@ -473,13 +475,14 @@
     static PipTransitionController providePipTransitionController(Context context,
             ShellInit shellInit, ShellTaskOrganizer shellTaskOrganizer, Transitions transitions,
             PipAnimationController pipAnimationController, PipBoundsAlgorithm pipBoundsAlgorithm,
-            PipBoundsState pipBoundsState, PipTransitionState pipTransitionState,
-            PhonePipMenuController pipMenuController,
+            PipBoundsState pipBoundsState, PipSizeSpecHandler pipSizeSpecHandler,
+            PipTransitionState pipTransitionState, PhonePipMenuController pipMenuController,
             PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
             Optional<SplitScreenController> splitScreenOptional) {
         return new PipTransition(context, shellInit, shellTaskOrganizer, transitions,
-                pipBoundsState, pipTransitionState, pipMenuController, pipBoundsAlgorithm,
-                pipAnimationController, pipSurfaceTransactionHelper, splitScreenOptional);
+                pipBoundsState, pipSizeSpecHandler, pipTransitionState, pipMenuController,
+                pipBoundsAlgorithm, pipAnimationController, pipSurfaceTransactionHelper,
+                splitScreenOptional);
     }
 
     @WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
index a839a23..d850416 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
@@ -253,7 +253,8 @@
      */
     void showDesktopApps() {
         // Bring apps to front, ignoring their visibility status to always ensure they are on top.
-        WindowContainerTransaction wct = bringDesktopAppsToFront(true /* ignoreVisibility */);
+        WindowContainerTransaction wct = new WindowContainerTransaction();
+        bringDesktopAppsToFront(wct);
 
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
             mTransitions.startTransition(TRANSIT_TO_FRONT, wct, null /* handler */);
@@ -267,9 +268,7 @@
         return mDesktopModeTaskRepository.getVisibleTaskCount();
     }
 
-    @NonNull
-    private WindowContainerTransaction bringDesktopAppsToFront(boolean force) {
-        final WindowContainerTransaction wct = new WindowContainerTransaction();
+    private void bringDesktopAppsToFront(WindowContainerTransaction wct) {
         final ArraySet<Integer> activeTasks = mDesktopModeTaskRepository.getActiveTasks();
         ProtoLog.d(WM_SHELL_DESKTOP_MODE, "bringDesktopAppsToFront: tasks=%s", activeTasks.size());
 
@@ -282,18 +281,11 @@
         }
 
         if (taskInfos.isEmpty()) {
-            return wct;
+            return;
         }
 
-        if (!force) {
-            final boolean allActiveTasksAreVisible = taskInfos.stream()
-                    .allMatch(info -> mDesktopModeTaskRepository.isVisibleTask(info.taskId));
-            if (allActiveTasksAreVisible) {
-                ProtoLog.d(WM_SHELL_DESKTOP_MODE,
-                        "bringDesktopAppsToFront: active tasks are already in front, skipping.");
-                return wct;
-            }
-        }
+        moveHomeTaskToFront(wct);
+
         ProtoLog.d(WM_SHELL_DESKTOP_MODE,
                 "bringDesktopAppsToFront: reordering all active tasks to the front");
         final List<Integer> allTasksInZOrder =
@@ -304,7 +296,15 @@
         for (RunningTaskInfo task : taskInfos) {
             wct.reorder(task.token, true);
         }
-        return wct;
+    }
+
+    private void moveHomeTaskToFront(WindowContainerTransaction wct) {
+        for (RunningTaskInfo task : mShellTaskOrganizer.getRunningTasks(mContext.getDisplayId())) {
+            if (task.getActivityType() == ACTIVITY_TYPE_HOME) {
+                wct.reorder(task.token, true /* onTop */);
+                return;
+            }
+        }
     }
 
     /**
@@ -363,7 +363,7 @@
         if (wct == null) {
             wct = new WindowContainerTransaction();
         }
-        wct.merge(bringDesktopAppsToFront(false /* ignoreVisibility */), true /* transfer */);
+        bringDesktopAppsToFront(wct);
         wct.reorder(request.getTriggerTask().token, true /* onTop */);
 
         return wct;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java
index e91987d..ac13f96 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java
@@ -16,6 +16,7 @@
 
 package com.android.wm.shell.kidsmode;
 
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
@@ -68,7 +69,7 @@
     private static final String TAG = "KidsModeTaskOrganizer";
 
     private static final int[] CONTROLLED_ACTIVITY_TYPES =
-            {ACTIVITY_TYPE_UNDEFINED, ACTIVITY_TYPE_STANDARD};
+            {ACTIVITY_TYPE_UNDEFINED, ACTIVITY_TYPE_STANDARD, ACTIVITY_TYPE_HOME};
     private static final int[] CONTROLLED_WINDOWING_MODES =
             {WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED};
 
@@ -93,6 +94,8 @@
     private KidsModeSettingsObserver mKidsModeSettingsObserver;
     private boolean mEnabled;
 
+    private ActivityManager.RunningTaskInfo mHomeTask;
+
     private final BroadcastReceiver mUserSwitchIntentReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -219,6 +222,13 @@
         }
         super.onTaskAppeared(taskInfo, leash);
 
+        // Only allow home to draw under system bars.
+        if (taskInfo.getActivityType() == ACTIVITY_TYPE_HOME) {
+            final WindowContainerTransaction wct = getWindowContainerTransaction();
+            wct.setBounds(taskInfo.token, new Rect(0, 0, mDisplayWidth, mDisplayHeight));
+            mSyncQueue.queue(wct);
+            mHomeTask = taskInfo;
+        }
         mSyncQueue.runInSync(t -> {
             // Reset several properties back to fullscreen (PiP, for example, leaves all these
             // properties in a bad state).
@@ -291,6 +301,13 @@
         }
         mLaunchRootTask = null;
         mLaunchRootLeash = null;
+        if (mHomeTask != null && mHomeTask.token != null) {
+            final WindowContainerToken homeToken = mHomeTask.token;
+            final WindowContainerTransaction wct = getWindowContainerTransaction();
+            wct.setBounds(homeToken, null);
+            mSyncQueue.queue(wct);
+        }
+        mHomeTask = null;
         unregisterOrganizer();
     }
 
@@ -320,7 +337,7 @@
             final SurfaceControl rootLeash = mLaunchRootLeash;
             mSyncQueue.runInSync(t -> {
                 t.setPosition(rootLeash, taskBounds.left, taskBounds.top);
-                t.setWindowCrop(rootLeash, taskBounds.width(), taskBounds.height());
+                t.setWindowCrop(rootLeash, mDisplayWidth, mDisplayHeight);
             });
         }
     }
@@ -351,7 +368,7 @@
         final SurfaceControl finalLeash = mLaunchRootLeash;
         mSyncQueue.runInSync(t -> {
             t.setPosition(finalLeash, taskBounds.left, taskBounds.top);
-            t.setWindowCrop(finalLeash, taskBounds.width(), taskBounds.height());
+            t.setWindowCrop(finalLeash, mDisplayWidth, mDisplayHeight);
         });
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
index 61da10b..5be18d8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
@@ -311,10 +311,10 @@
         mDisplayLayout.set(displayLayout);
     }
 
-    /** Get the display layout. */
+    /** Get a copy of the display layout. */
     @NonNull
     public DisplayLayout getDisplayLayout() {
-        return mDisplayLayout;
+        return new DisplayLayout(mDisplayLayout);
     }
 
     @VisibleForTesting
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index aad27b9..ffb2893 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -79,11 +79,13 @@
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.animation.Interpolators;
 import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.common.ScreenshotUtils;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.annotations.ShellMainThread;
 import com.android.wm.shell.pip.phone.PipMotionHelper;
+import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.splitscreen.SplitScreenController;
 import com.android.wm.shell.transition.Transitions;
@@ -126,6 +128,7 @@
     private final Context mContext;
     private final SyncTransactionQueue mSyncTransactionQueue;
     private final PipBoundsState mPipBoundsState;
+    private final PipSizeSpecHandler mPipSizeSpecHandler;
     private final PipBoundsAlgorithm mPipBoundsAlgorithm;
     private final @NonNull PipMenuController mPipMenuController;
     private final PipAnimationController mPipAnimationController;
@@ -313,6 +316,7 @@
             @NonNull SyncTransactionQueue syncTransactionQueue,
             @NonNull PipTransitionState pipTransitionState,
             @NonNull PipBoundsState pipBoundsState,
+            @NonNull PipSizeSpecHandler pipSizeSpecHandler,
             @NonNull PipBoundsAlgorithm boundsHandler,
             @NonNull PipMenuController pipMenuController,
             @NonNull PipAnimationController pipAnimationController,
@@ -328,6 +332,7 @@
         mSyncTransactionQueue = syncTransactionQueue;
         mPipTransitionState = pipTransitionState;
         mPipBoundsState = pipBoundsState;
+        mPipSizeSpecHandler = pipSizeSpecHandler;
         mPipBoundsAlgorithm = boundsHandler;
         mPipMenuController = pipMenuController;
         mPipTransitionController = pipTransitionController;
@@ -1601,7 +1606,12 @@
     private @Nullable Rect computeRotatedBounds(int rotationDelta, int direction,
             Rect outDestinationBounds, Rect sourceHintRect) {
         if (direction == TRANSITION_DIRECTION_TO_PIP) {
-            mPipBoundsState.getDisplayLayout().rotateTo(mContext.getResources(), mNextRotation);
+            DisplayLayout layoutCopy = mPipBoundsState.getDisplayLayout();
+
+            layoutCopy.rotateTo(mContext.getResources(), mNextRotation);
+            mPipBoundsState.setDisplayLayout(layoutCopy);
+            mPipSizeSpecHandler.setDisplayLayout(layoutCopy);
+
             final Rect displayBounds = mPipBoundsState.getDisplayBounds();
             outDestinationBounds.set(mPipBoundsAlgorithm.getEntryDestinationBounds());
             // Transform the destination bounds to current display coordinates.
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 d9d1009..e5c0570 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
@@ -65,6 +65,8 @@
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.R;
 import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.splitscreen.SplitScreenController;
 import com.android.wm.shell.sysui.ShellInit;
@@ -83,6 +85,7 @@
 
     private final Context mContext;
     private final PipTransitionState mPipTransitionState;
+    private final PipSizeSpecHandler mPipSizeSpecHandler;
     private final int mEnterExitAnimationDuration;
     private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
     private final Optional<SplitScreenController> mSplitScreenOptional;
@@ -113,6 +116,7 @@
             @NonNull ShellTaskOrganizer shellTaskOrganizer,
             @NonNull Transitions transitions,
             PipBoundsState pipBoundsState,
+            PipSizeSpecHandler pipSizeSpecHandler,
             PipTransitionState pipTransitionState,
             PipMenuController pipMenuController,
             PipBoundsAlgorithm pipBoundsAlgorithm,
@@ -123,6 +127,7 @@
                 pipBoundsAlgorithm, pipAnimationController);
         mContext = context;
         mPipTransitionState = pipTransitionState;
+        mPipSizeSpecHandler = pipSizeSpecHandler;
         mEnterExitAnimationDuration = context.getResources()
                 .getInteger(R.integer.config_pipResizeAnimationDuration);
         mSurfaceTransactionHelper = pipSurfaceTransactionHelper;
@@ -308,7 +313,12 @@
             // initial state under the new rotation.
             int rotationDelta = deltaRotation(startRotation, endRotation);
             if (rotationDelta != Surface.ROTATION_0) {
-                mPipBoundsState.getDisplayLayout().rotateTo(mContext.getResources(), endRotation);
+                DisplayLayout layoutCopy = mPipBoundsState.getDisplayLayout();
+
+                layoutCopy.rotateTo(mContext.getResources(), endRotation);
+                mPipBoundsState.setDisplayLayout(layoutCopy);
+                mPipSizeSpecHandler.setDisplayLayout(layoutCopy);
+
                 final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
                 wct.setBounds(mRequestedEnterTask, destinationBounds);
                 return true;
@@ -824,7 +834,12 @@
     /** Computes destination bounds in old rotation and updates source hint rect if available. */
     private void computeEnterPipRotatedBounds(int rotationDelta, int startRotation, int endRotation,
             TaskInfo taskInfo, Rect outDestinationBounds, @Nullable Rect outSourceHintRect) {
-        mPipBoundsState.getDisplayLayout().rotateTo(mContext.getResources(), endRotation);
+        DisplayLayout layoutCopy = mPipBoundsState.getDisplayLayout();
+
+        layoutCopy.rotateTo(mContext.getResources(), endRotation);
+        mPipBoundsState.setDisplayLayout(layoutCopy);
+        mPipSizeSpecHandler.setDisplayLayout(layoutCopy);
+
         final Rect displayBounds = mPipBoundsState.getDisplayBounds();
         outDestinationBounds.set(mPipBoundsAlgorithm.getEntryDestinationBounds());
         // Transform the destination bounds to current display coordinates.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index d86468a..4958b25 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -1032,7 +1032,11 @@
     private void onDisplayRotationChangedNotInPip(Context context, int toRotation) {
         // Update the display layout, note that we have to do this on every rotation even if we
         // aren't in PIP since we need to update the display layout to get the right resources
-        mPipBoundsState.getDisplayLayout().rotateTo(context.getResources(), toRotation);
+        DisplayLayout layoutCopy = mPipBoundsState.getDisplayLayout();
+
+        layoutCopy.rotateTo(context.getResources(), toRotation);
+        mPipBoundsState.setDisplayLayout(layoutCopy);
+        mPipSizeSpecHandler.setDisplayLayout(layoutCopy);
     }
 
     /**
@@ -1069,7 +1073,11 @@
                         mPipBoundsState.getStashedState());
 
         // Update the display layout
-        mPipBoundsState.getDisplayLayout().rotateTo(context.getResources(), toRotation);
+        DisplayLayout layoutCopy = mPipBoundsState.getDisplayLayout();
+
+        layoutCopy.rotateTo(context.getResources(), toRotation);
+        mPipBoundsState.setDisplayLayout(layoutCopy);
+        mPipSizeSpecHandler.setDisplayLayout(layoutCopy);
 
         // Calculate the stack bounds in the new orientation based on same fraction along the
         // rotated movement bounds.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java
index e787ed9..d03d075 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java
@@ -409,12 +409,6 @@
         return new Rect(0, 0, mDisplayLayout.width(), mDisplayLayout.height());
     }
 
-    /** Get the display layout. */
-    @NonNull
-    public DisplayLayout getDisplayLayout() {
-        return mDisplayLayout;
-    }
-
     /** Update the display layout. */
     public void setDisplayLayout(@NonNull DisplayLayout displayLayout) {
         mDisplayLayout.set(displayLayout);
@@ -429,12 +423,11 @@
      */
     public Rect getInsetBounds() {
         Rect insetBounds = new Rect();
-        final DisplayLayout displayLayout = getDisplayLayout();
-        Rect insets = getDisplayLayout().stableInsets();
+        Rect insets = mDisplayLayout.stableInsets();
         insetBounds.set(insets.left + mScreenEdgeInsets.x,
                 insets.top + mScreenEdgeInsets.y,
-                displayLayout.width() - insets.right - mScreenEdgeInsets.x,
-                displayLayout.height() - insets.bottom - mScreenEdgeInsets.y);
+                mDisplayLayout.width() - insets.right - mScreenEdgeInsets.x,
+                mDisplayLayout.height() - insets.bottom - mScreenEdgeInsets.y);
         return insetBounds;
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
index 42fd1aa..be9b936 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
@@ -36,6 +36,7 @@
 import com.android.wm.shell.pip.PipTransitionState;
 import com.android.wm.shell.pip.PipUiEventLogger;
 import com.android.wm.shell.pip.PipUtils;
+import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
 import com.android.wm.shell.splitscreen.SplitScreenController;
 
 import java.util.Objects;
@@ -50,6 +51,7 @@
             @NonNull SyncTransactionQueue syncTransactionQueue,
             @NonNull PipTransitionState pipTransitionState,
             @NonNull PipBoundsState pipBoundsState,
+            @NonNull PipSizeSpecHandler pipSizeSpecHandler,
             @NonNull PipBoundsAlgorithm boundsHandler,
             @NonNull PipMenuController pipMenuController,
             @NonNull PipAnimationController pipAnimationController,
@@ -61,10 +63,11 @@
             @NonNull PipUiEventLogger pipUiEventLogger,
             @NonNull ShellTaskOrganizer shellTaskOrganizer,
             ShellExecutor mainExecutor) {
-        super(context, syncTransactionQueue, pipTransitionState, pipBoundsState, boundsHandler,
-                pipMenuController, pipAnimationController, surfaceTransactionHelper,
-                pipTransitionController, pipParamsChangedForwarder, splitScreenOptional,
-                displayController, pipUiEventLogger, shellTaskOrganizer, mainExecutor);
+        super(context, syncTransactionQueue, pipTransitionState, pipBoundsState, pipSizeSpecHandler,
+                boundsHandler, pipMenuController, pipAnimationController,
+                surfaceTransactionHelper, pipTransitionController, pipParamsChangedForwarder,
+                splitScreenOptional, displayController, pipUiEventLogger, shellTaskOrganizer,
+                mainExecutor);
     }
 
     @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
index 56aa742..81e118a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
@@ -122,9 +122,9 @@
      * Start a pair of intents using legacy transition system.
      */
     oneway void startIntentsWithLegacyTransition(in PendingIntent pendingIntent1,
-            in Bundle options1, in PendingIntent pendingIntent2, in Bundle options2,
-            int splitPosition, float splitRatio, in RemoteAnimationAdapter adapter,
-            in InstanceId instanceId) = 18;
+            in ShortcutInfo shortcutInfo1, in Bundle options1, in PendingIntent pendingIntent2,
+            in ShortcutInfo shortcutInfo2, in Bundle options2, int splitPosition, float splitRatio,
+            in RemoteAnimationAdapter adapter, in InstanceId instanceId) = 18;
 
     /**
      * Start a pair of intents in one transition.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 7cb5cf2..36da4cf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -583,9 +583,10 @@
     }
 
     private void startIntentsWithLegacyTransition(PendingIntent pendingIntent1,
-            @Nullable Bundle options1, PendingIntent pendingIntent2,
-            @Nullable Bundle options2, @SplitPosition int splitPosition,
-            float splitRatio, RemoteAnimationAdapter adapter, InstanceId instanceId) {
+            @Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1,
+            PendingIntent pendingIntent2, @Nullable ShortcutInfo shortcutInfo2,
+            @Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio,
+            RemoteAnimationAdapter adapter, InstanceId instanceId) {
         Intent fillInIntent1 = null;
         Intent fillInIntent2 = null;
         final String packageName1 = SplitScreenUtils.getPackageName(pendingIntent1);
@@ -605,9 +606,9 @@
                         Toast.LENGTH_SHORT).show();
             }
         }
-        mStageCoordinator.startIntentsWithLegacyTransition(pendingIntent1, fillInIntent1, options1,
-                pendingIntent2, fillInIntent2, options2, splitPosition, splitRatio, adapter,
-                instanceId);
+        mStageCoordinator.startIntentsWithLegacyTransition(pendingIntent1, fillInIntent1,
+                shortcutInfo1, options1, pendingIntent2, fillInIntent2, shortcutInfo2, options2,
+                splitPosition, splitRatio, adapter, instanceId);
     }
 
     @Override
@@ -1037,13 +1038,14 @@
 
         @Override
         public void startIntentsWithLegacyTransition(PendingIntent pendingIntent1,
-                @Nullable Bundle options1, PendingIntent pendingIntent2, @Nullable Bundle options2,
-                @SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
-                InstanceId instanceId) {
+                @Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1,
+                PendingIntent pendingIntent2, @Nullable ShortcutInfo shortcutInfo2,
+                @Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio,
+                RemoteAnimationAdapter adapter, InstanceId instanceId) {
             executeRemoteCallWithTaskPermission(mController, "startIntentsWithLegacyTransition",
                     (controller) ->
-                        controller.startIntentsWithLegacyTransition(
-                                pendingIntent1, options1, pendingIntent2, options2, splitPosition,
+                        controller.startIntentsWithLegacyTransition(pendingIntent1, shortcutInfo1,
+                                options1, pendingIntent2, shortcutInfo2, options2, splitPosition,
                                 splitRatio, adapter, instanceId)
                     );
         }
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 5a9170b..2a4bae9 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
@@ -692,9 +692,11 @@
 
     /** Starts a pair of intents using legacy transition. */
     void startIntentsWithLegacyTransition(PendingIntent pendingIntent1, Intent fillInIntent1,
-            @Nullable Bundle options1, PendingIntent pendingIntent2, Intent fillInIntent2,
-            @Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio,
-            RemoteAnimationAdapter adapter, InstanceId instanceId) {
+            @Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1,
+            @Nullable PendingIntent pendingIntent2, Intent fillInIntent2,
+            @Nullable ShortcutInfo shortcutInfo2, @Nullable Bundle options2,
+            @SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
+            InstanceId instanceId) {
         final WindowContainerTransaction wct = new WindowContainerTransaction();
         if (options1 == null) options1 = new Bundle();
         if (pendingIntent2 == null) {
@@ -703,15 +705,23 @@
             activityOptions.update(ActivityOptions.makeRemoteAnimation(adapter));
             options1 = activityOptions.toBundle();
             addActivityOptions(options1, null /* launchTarget */);
-            wct.sendPendingIntent(pendingIntent1, fillInIntent1, options1);
+            if (shortcutInfo1 != null) {
+                wct.startShortcut(mContext.getPackageName(), shortcutInfo1, options1);
+            } else {
+                wct.sendPendingIntent(pendingIntent1, fillInIntent1, options1);
+            }
             mSyncQueue.queue(wct);
             return;
         }
 
         addActivityOptions(options1, mSideStage);
-        wct.sendPendingIntent(pendingIntent1, fillInIntent1, options1);
-        startWithLegacyTransition(wct, pendingIntent2, fillInIntent2, options2, splitPosition,
-                splitRatio, adapter, instanceId);
+        if (shortcutInfo1 != null) {
+            wct.startShortcut(mContext.getPackageName(), shortcutInfo1, options1);
+        } else {
+            wct.sendPendingIntent(pendingIntent1, fillInIntent1, options1);
+        }
+        startWithLegacyTransition(wct, pendingIntent2, fillInIntent2, shortcutInfo2, options2,
+                splitPosition, splitRatio, adapter, instanceId);
     }
 
     void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent, Intent fillInIntent,
@@ -763,18 +773,19 @@
 
     private void startWithLegacyTransition(WindowContainerTransaction wct,
             @Nullable PendingIntent mainPendingIntent, @Nullable Intent mainFillInIntent,
-            @Nullable Bundle mainOptions, @SplitPosition int sidePosition, float splitRatio,
-            RemoteAnimationAdapter adapter, InstanceId instanceId) {
+            @Nullable ShortcutInfo mainShortcutInfo, @Nullable Bundle mainOptions,
+            @SplitPosition int sidePosition, float splitRatio, RemoteAnimationAdapter adapter,
+            InstanceId instanceId) {
         startWithLegacyTransition(wct, INVALID_TASK_ID, mainPendingIntent, mainFillInIntent,
-                mainOptions, sidePosition, splitRatio, adapter, instanceId);
+                mainShortcutInfo, mainOptions, sidePosition, splitRatio, adapter, instanceId);
     }
 
     private void startWithLegacyTransition(WindowContainerTransaction wct, int mainTaskId,
             @Nullable Bundle mainOptions, @SplitPosition int sidePosition, float splitRatio,
             RemoteAnimationAdapter adapter, InstanceId instanceId) {
         startWithLegacyTransition(wct, mainTaskId, null /* mainPendingIntent */,
-                null /* mainFillInIntent */, mainOptions, sidePosition, splitRatio, adapter,
-                instanceId);
+                null /* mainFillInIntent */, null /* mainShortcutInfo */, mainOptions, sidePosition,
+                splitRatio, adapter, instanceId);
     }
 
     /**
@@ -784,8 +795,9 @@
      */
     private void startWithLegacyTransition(WindowContainerTransaction wct, int mainTaskId,
             @Nullable PendingIntent mainPendingIntent, @Nullable Intent mainFillInIntent,
-            @Nullable Bundle mainOptions, @SplitPosition int sidePosition, float splitRatio,
-            RemoteAnimationAdapter adapter, InstanceId instanceId) {
+            @Nullable ShortcutInfo mainShortcutInfo, @Nullable Bundle options,
+            @SplitPosition int sidePosition, float splitRatio, RemoteAnimationAdapter adapter,
+            InstanceId instanceId) {
         if (!isSplitScreenVisible()) {
             exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RECREATE_SPLIT);
         }
@@ -809,15 +821,19 @@
             mMainStage.activate(wct, false /* reparent */);
         }
 
-        if (mainOptions == null) mainOptions = new Bundle();
-        addActivityOptions(mainOptions, mMainStage);
-        mainOptions = wrapAsSplitRemoteAnimation(adapter, mainOptions);
+        if (options == null) options = new Bundle();
+        addActivityOptions(options, mMainStage);
+        options = wrapAsSplitRemoteAnimation(adapter, options);
 
         updateWindowBounds(mSplitLayout, wct);
-        if (mainTaskId == INVALID_TASK_ID) {
-            wct.sendPendingIntent(mainPendingIntent, mainFillInIntent, mainOptions);
+
+        // TODO(b/268008375): Merge APIs to start a split pair into one.
+        if (mainTaskId != INVALID_TASK_ID) {
+            wct.startTask(mainTaskId, options);
+        } else if (mainShortcutInfo != null) {
+            wct.startShortcut(mContext.getPackageName(), mainShortcutInfo, options);
         } else {
-            wct.startTask(mainTaskId, mainOptions);
+            wct.sendPendingIntent(mainPendingIntent, mainFillInIntent, options);
         }
 
         wct.reorder(mRootTaskInfo.token, true);
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 b2f61c2..5275e90 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
@@ -829,7 +829,15 @@
         }
         mOrganizer.startTransition(transitionToken, wct != null && wct.isEmpty() ? null : wct);
         active.mToken = transitionToken;
-        mActiveTransitions.add(active);
+        int insertIdx = 0;
+        for (; insertIdx < mActiveTransitions.size(); ++insertIdx) {
+            if (mActiveTransitions.get(insertIdx).mInfo == null) {
+                // A `startNewTransition` was sent to WMCore, but wasn't acknowledged before WMCore
+                // made this request, so insert this request beforehand to keep order in sync.
+                break;
+            }
+        }
+        mActiveTransitions.add(insertIdx, active);
     }
 
     /** Start a new transition directly. */
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
index 2da4af8..e907cd3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
@@ -104,8 +104,8 @@
                 new PipSnapAlgorithm(), new PipKeepClearAlgorithmInterface() {},
                 mPipSizeSpecHandler);
         mMainExecutor = new TestShellExecutor();
-        mPipTaskOrganizer = new PipTaskOrganizer(mContext,
-                mMockSyncTransactionQueue, mPipTransitionState, mPipBoundsState,
+        mPipTaskOrganizer = new PipTaskOrganizer(mContext, mMockSyncTransactionQueue,
+                mPipTransitionState, mPipBoundsState, mPipSizeSpecHandler,
                 mPipBoundsAlgorithm, mMockPhonePipMenuController, mMockPipAnimationController,
                 mMockPipSurfaceTransactionHelper, mMockPipTransitionController,
                 mMockPipParamsChangedForwarder, mMockOptionalSplitScreen, mMockDisplayController,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index 595c3b4..a9061ae 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -45,6 +45,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.Mockito.clearInvocations;
@@ -119,8 +120,8 @@
 
     @Before
     public void setUp() {
-        doAnswer(invocation -> invocation.getArguments()[1])
-                .when(mOrganizer).startTransition(any(), any());
+        doAnswer(invocation -> new Binder())
+                .when(mOrganizer).startNewTransition(anyInt(), any());
     }
 
     @Test
@@ -562,6 +563,32 @@
     }
 
     @Test
+    public void testTransitionOrderMatchesCore() {
+        Transitions transitions = createTestTransitions();
+        transitions.replaceDefaultHandlerForTest(mDefaultHandler);
+
+        IBinder transitToken = new Binder();
+        IBinder shellInit = transitions.startTransition(TRANSIT_CLOSE,
+                new WindowContainerTransaction(), null /* handler */);
+        // make sure we are testing the "New" API.
+        verify(mOrganizer, times(1)).startNewTransition(eq(TRANSIT_CLOSE), any());
+        // WMCore may not receive the new transition before requesting its own.
+        transitions.requestStartTransition(transitToken,
+                new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
+        verify(mOrganizer, times(1)).startTransition(eq(transitToken), any());
+
+        // At this point, WM is working on its transition (the shell-initialized one is still
+        // queued), so continue the transition lifecycle for that.
+        TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN)
+                .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
+        transitions.onTransitionReady(transitToken, info, mock(SurfaceControl.Transaction.class),
+                mock(SurfaceControl.Transaction.class));
+        // At this point, if things are not working, we'd get an NPE due to attempting to merge
+        // into the shellInit transition which hasn't started yet.
+        assertEquals(1, mDefaultHandler.activeCount());
+    }
+
+    @Test
     public void testShouldRotateSeamlessly() throws Exception {
         final RunningTaskInfo taskInfo =
                 createTaskInfo(1, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java
index dea6097..b0cdb05 100644
--- a/media/java/android/media/AudioAttributes.java
+++ b/media/java/android/media/AudioAttributes.java
@@ -1275,7 +1275,10 @@
                     || (preset == MediaRecorder.AudioSource.VOICE_UPLINK)
                     || (preset == MediaRecorder.AudioSource.VOICE_CALL)
                     || (preset == MediaRecorder.AudioSource.ECHO_REFERENCE)
-                    || (preset == MediaRecorder.AudioSource.ULTRASOUND)) {
+                    || (preset == MediaRecorder.AudioSource.ULTRASOUND)
+                    // AUDIO_SOURCE_INVALID is used by convention on default initialized
+                    // audio attributes
+                    || (preset == MediaRecorder.AudioSource.AUDIO_SOURCE_INVALID)) {
                 mSource = preset;
             } else {
                 setCapturePreset(preset);
diff --git a/media/java/android/media/tv/TableRequest.java b/media/java/android/media/tv/TableRequest.java
index a9ea6d3..d9587f6 100644
--- a/media/java/android/media/tv/TableRequest.java
+++ b/media/java/android/media/tv/TableRequest.java
@@ -33,7 +33,8 @@
 
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
-    @IntDef({TABLE_NAME_PAT, TABLE_NAME_PMT, TABLE_NAME_CAT})
+    @IntDef({TABLE_NAME_PAT, TABLE_NAME_PMT, TABLE_NAME_CAT, TABLE_NAME_NIT, TABLE_NAME_BAT,
+            TABLE_NAME_SDT, TABLE_NAME_EIT, TABLE_NAME_TDT, TABLE_NAME_TOT, TABLE_NAME_SIT})
     public @interface TableName {}
 
     /** Program Association Table */
@@ -42,42 +43,34 @@
     public static final int TABLE_NAME_PMT = 1;
     /**
      * Conditional Access Table
-     * @hide
      */
     public static final int TABLE_NAME_CAT = 2;
     /**
      * Network Information Table
-     * @hide
      */
     public static final int TABLE_NAME_NIT = 3;
     /**
      * Bouquet Association Table
-     * @hide
      */
     public static final int TABLE_NAME_BAT = 4;
     /**
      * Service Description Table
-     * @hide
      */
     public static final int TABLE_NAME_SDT = 5;
     /**
      * Event Information Table
-     * @hide
      */
     public static final int TABLE_NAME_EIT = 6;
     /**
      * Time and Date Table
-     * @hide
      */
     public static final int TABLE_NAME_TDT = 7;
     /**
      * Time Offset Table
-     * @hide
      */
     public static final int TABLE_NAME_TOT = 8;
     /**
      * Selection Information Table
-     * @hide
      */
     public static final int TABLE_NAME_SIT = 9;
 
diff --git a/media/java/android/media/tv/TableResponse.java b/media/java/android/media/tv/TableResponse.java
index 1c314b0..fb4e99c 100644
--- a/media/java/android/media/tv/TableResponse.java
+++ b/media/java/android/media/tv/TableResponse.java
@@ -54,6 +54,17 @@
         return new TableResponse(in);
     }
 
+    /**
+     * Constructs a TableResponse with a table URI.
+     *
+     * @param requestId The ID is used to associate the response with the request.
+     * @param sequence The sequence number which indicates the order of related responses.
+     * @param responseResult The result for the response. It's one of {@link #RESPONSE_RESULT_OK},
+     *                       {@link #RESPONSE_RESULT_CANCEL}, {@link #RESPONSE_RESULT_ERROR}.
+     * @param tableUri The URI of the table in the database.
+     * @param version The version number of requested table.
+     * @param size The Size number of table in bytes.
+     */
     public TableResponse(int requestId, int sequence, @ResponseResult int responseResult,
             @Nullable Uri tableUri, int version, int size) {
         super(RESPONSE_TYPE, requestId, sequence, responseResult);
@@ -64,7 +75,19 @@
         mTableSharedMemory = null;
     }
 
-    /** @hide */
+    /**
+     * Constructs a TableResponse with a table URI.
+     *
+     * @param requestId The ID is used to associate the response with the request.
+     * @param sequence The sequence number which indicates the order of related responses.
+     * @param responseResult The result for the response. It's one of {@link #RESPONSE_RESULT_OK},
+     *                       {@link #RESPONSE_RESULT_CANCEL}, {@link #RESPONSE_RESULT_ERROR}.
+     * @param tableByteArray The byte array which stores the table in bytes. The structure and
+     *                       syntax of the table depends on the table name in
+     *                       {@link TableRequest#getTableName()} and the corresponding standard.
+     * @param version The version number of requested table.
+     * @param size The Size number of table in bytes.
+     */
     public TableResponse(int requestId, int sequence, @ResponseResult int responseResult,
             @NonNull byte[] tableByteArray, int version, int size) {
         super(RESPONSE_TYPE, requestId, sequence, responseResult);
@@ -75,7 +98,21 @@
         mTableSharedMemory = null;
     }
 
-    /** @hide */
+    /**
+     * Constructs a TableResponse with a table URI.
+     *
+     * @param requestId The ID is used to associate the response with the request.
+     * @param sequence The sequence number which indicates the order of related responses.
+     * @param responseResult The result for the response. It's one of {@link #RESPONSE_RESULT_OK},
+     *                       {@link #RESPONSE_RESULT_CANCEL}, {@link #RESPONSE_RESULT_ERROR}.
+     * @param tableSharedMemory The shared memory which stores the table. The table size can be
+     *                          large so using a shared memory optimizes the data
+     *                          communication between the table data source and the receiver. The
+     *                          structure syntax of the table depends on the table name in
+     *                          {@link TableRequest#getTableName()} and the corresponding standard.
+     * @param version The version number of requested table.
+     * @param size The Size number of table in bytes.
+     */
     public TableResponse(int requestId, int sequence, @ResponseResult int responseResult,
             @NonNull SharedMemory tableSharedMemory, int version, int size) {
         super(RESPONSE_TYPE, requestId, sequence, responseResult);
@@ -115,7 +152,6 @@
      *
      * @return the table data as a byte array, or {@code null} if the data is not stored as a byte
      *         array.
-     * @hide
      */
     @Nullable
     public byte[] getTableByteArray() {
@@ -125,9 +161,14 @@
     /**
      * Gets the data of the table as a {@link SharedMemory} object.
      *
+     * <p> This data lives in a {@link SharedMemory} instance because of the potentially large
+     * amount of data needed to store the table. This optimizes the data communication between the
+     * table data source and the receiver.
+     *
      * @return the table data as a {@link SharedMemory} object, or {@code null} if the data is not
      *         stored in shared memory.
-     * @hide
+     *
+     * @see SharedMemory#map(int, int, int)
      */
     @Nullable
     public SharedMemory getTableSharedMemory() {
diff --git a/media/java/android/media/tv/TimelineRequest.java b/media/java/android/media/tv/TimelineRequest.java
index d04c58a..77613dc 100644
--- a/media/java/android/media/tv/TimelineRequest.java
+++ b/media/java/android/media/tv/TimelineRequest.java
@@ -17,6 +17,7 @@
 package android.media.tv;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -54,7 +55,6 @@
         mSelector = null;
     }
 
-    /** @hide */
     public TimelineRequest(int requestId, @RequestOption int option, int intervalMillis,
             @NonNull String selector) {
         super(REQUEST_TYPE, requestId, option);
@@ -81,8 +81,8 @@
      * {@code urn:dvb:css:timeline:pts} is a selector in DVB standard.
      *
      * @return the selector if it's set; {@code null} otherwise.
-     * @hide
      */
+    @Nullable
     public String getSelector() {
         return mSelector;
     }
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
index 999ff0d..80b6e21 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
@@ -145,30 +145,29 @@
 
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
-    @IntDef(prefix = "PLAYBACK_COMMAND_STOP_MODE_", value = {
-            PLAYBACK_COMMAND_STOP_MODE_BLANK,
-            PLAYBACK_COMMAND_STOP_MODE_FREEZE
+    @IntDef(prefix = "COMMAND_PARAMETER_VALUE_STOP_MODE_", value = {
+            COMMAND_PARAMETER_VALUE_STOP_MODE_BLANK,
+            COMMAND_PARAMETER_VALUE_STOP_MODE_FREEZE
     })
     public @interface PlaybackCommandStopMode {}
 
     /**
      * Playback command stop mode: show a blank screen.
-     * @hide
+     * @see #PLAYBACK_COMMAND_TYPE_STOP
      */
-    public static final int PLAYBACK_COMMAND_STOP_MODE_BLANK = 1;
+    public static final int COMMAND_PARAMETER_VALUE_STOP_MODE_BLANK = 1;
 
     /**
      * Playback command stop mode: freeze the video.
-     * @hide
+     * @see #PLAYBACK_COMMAND_TYPE_STOP
      */
-    public static final int PLAYBACK_COMMAND_STOP_MODE_FREEZE = 2;
+    public static final int COMMAND_PARAMETER_VALUE_STOP_MODE_FREEZE = 2;
 
     /**
      * Playback command parameter: stop mode.
      * <p>Type: int
      *
      * @see #PLAYBACK_COMMAND_TYPE_STOP
-     * @hide
      */
     public static final String COMMAND_PARAMETER_KEY_STOP_MODE = "command_stop_mode";
 
@@ -550,7 +549,8 @@
 
         /**
          * Receives current video bounds.
-         * @hide
+         *
+         * @param bounds the rectangle area for rendering the current video.
          */
         public void onCurrentVideoBounds(@NonNull Rect bounds) {
         }
@@ -1135,7 +1135,6 @@
 
         /**
          * Requests the bounds of the current video.
-         * @hide
          */
         @CallSuper
         public void requestCurrentVideoBounds() {
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppView.java b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
index 29a96f7..2c40f39 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppView.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
@@ -516,7 +516,8 @@
 
     /**
      * Sends current video bounds to related TV interactive app.
-     * @hide
+     *
+     * @param bounds the rectangle area for rendering the current video.
      */
     public void sendCurrentVideoBounds(@NonNull Rect bounds) {
         if (DEBUG) {
@@ -1131,7 +1132,6 @@
          * is called.
          *
          * @param iAppServiceId The ID of the TV interactive app service bound to this view.
-         * @hide
          */
         public void onRequestCurrentVideoBounds(@NonNull String iAppServiceId) {
         }
diff --git a/packages/CredentialManager/AndroidManifest.xml b/packages/CredentialManager/AndroidManifest.xml
index 5a4d256..499d130 100644
--- a/packages/CredentialManager/AndroidManifest.xml
+++ b/packages/CredentialManager/AndroidManifest.xml
@@ -32,7 +32,6 @@
       android:supportsRtl="true"
       android:theme="@style/Theme.CredentialSelector">
 
-    <!--TODO: make sure implementing singleTop on NewIntent-->
     <activity
         android:name=".CredentialSelectorActivity"
         android:exported="true"
diff --git a/packages/CredentialManager/res/drawable/ic_passkeys_onboarding.png b/packages/CredentialManager/res/drawable/ic_passkeys_onboarding.png
deleted file mode 100644
index 388d098..0000000
--- a/packages/CredentialManager/res/drawable/ic_passkeys_onboarding.png
+++ /dev/null
Binary files differ
diff --git a/packages/CredentialManager/res/drawable/ic_passkeys_onboarding.xml b/packages/CredentialManager/res/drawable/ic_passkeys_onboarding.xml
new file mode 100644
index 0000000..91b23cc
--- /dev/null
+++ b/packages/CredentialManager/res/drawable/ic_passkeys_onboarding.xml
@@ -0,0 +1,158 @@
+<!--
+  ~ 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.
+  -->
+
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:name="vector"
+    android:width="316dp"
+    android:height="168dp"
+    android:viewportWidth="316"
+    android:viewportHeight="168">
+    <path
+        android:name="path"
+        android:pathData="M 238 56 C 238 42.75 227.26 32 214 32 C 200.75 32 190 42.75 190 56 L 190 74 C 190 77.31 192.69 80 196 80 L 214 80 C 227.26 80 238 69.26 238 56 Z"
+        android:fillColor="#ffba29"
+        android:strokeWidth="1"/>
+    <path
+        android:name="path_1"
+        android:pathData="M 199.42 51.71 C 199.34 51.65 199.29 51.56 199.27 51.44 C 199.26 51.31 199.27 51.21 199.33 51.13 C 201.08 48.8 203.23 46.98 205.79 45.67 C 208.35 44.36 211.07 43.71 213.96 43.71 C 216.85 43.71 219.64 44.34 222.25 45.61 C 224.86 46.88 227.03 48.69 228.75 51.05 C 228.86 51.19 228.9 51.32 228.85 51.45 C 228.81 51.57 228.72 51.68 228.58 51.76 C 228.5 51.82 228.38 51.84 228.23 51.84 C 228.08 51.84 227.96 51.76 227.88 51.59 C 226.27 49.34 224.23 47.63 221.78 46.47 C 219.32 45.3 216.72 44.72 213.97 44.72 C 211.22 44.72 208.68 45.32 206.26 46.51 C 203.84 47.7 201.83 49.4 200.22 51.59 C 200.08 51.76 199.94 51.84 199.8 51.84 C 199.66 51.84 199.54 51.8 199.42 51.72 Z M 218.75 72.42 C 216 71.7 213.72 70.28 211.9 68.17 C 210.08 66.06 209.17 63.49 209.17 60.46 C 209.17 59.13 209.65 58.02 210.61 57.15 C 211.57 56.27 212.71 55.84 214.05 55.84 C 215.39 55.84 216.48 56.28 217.43 57.15 C 218.37 58.02 218.85 59.13 218.85 60.46 C 218.85 61.49 219.23 62.34 220 63.02 C 220.76 63.7 221.66 64.04 222.69 64.04 C 223.72 64.04 224.64 63.69 225.38 63 C 226.12 62.31 226.48 61.46 226.48 60.46 C 226.48 57.13 225.25 54.32 222.79 52.04 C 220.33 49.76 217.39 48.62 213.98 48.62 C 210.57 48.62 207.63 49.76 205.19 52.04 C 202.75 54.32 201.52 57.12 201.52 60.46 C 201.52 61.13 201.61 61.97 201.79 63 C 201.97 64.03 202.28 65.22 202.73 66.58 C 202.79 66.72 202.79 66.84 202.75 66.93 C 202.71 67.03 202.6 67.12 202.44 67.2 C 202.3 67.28 202.17 67.3 202.04 67.24 C 201.91 67.18 201.82 67.09 201.77 66.95 C 201.35 65.78 201.04 64.68 200.83 63.64 C 200.62 62.6 200.52 61.54 200.52 60.45 C 200.52 56.81 201.85 53.76 204.5 51.3 C 207.15 48.84 210.31 47.61 213.98 47.61 C 217.65 47.61 220.86 48.84 223.52 51.3 C 226.19 53.76 227.52 56.81 227.52 60.45 C 227.52 61.73 227.05 62.81 226.1 63.7 C 225.16 64.59 224.02 65.03 222.68 65.03 C 221.34 65.03 220.24 64.59 219.28 63.7 C 218.32 62.81 217.84 61.73 217.84 60.45 C 217.84 59.42 217.46 58.56 216.72 57.87 C 215.98 57.18 215.08 56.83 214.05 56.83 C 213.02 56.83 212.08 57.18 211.32 57.87 C 210.56 58.56 210.17 59.43 210.17 60.45 C 210.17 63.26 211.01 65.6 212.69 67.47 C 214.37 69.35 216.49 70.66 219.04 71.41 C 219.21 71.47 219.32 71.55 219.39 71.66 C 219.46 71.77 219.47 71.9 219.41 72.04 C 219.35 72.15 219.28 72.25 219.18 72.35 C 219.08 72.45 218.94 72.47 218.74 72.41 Z M 204.42 43.04 C 204.36 43.04 204.27 43.03 204.15 43 C 204.02 42.97 203.95 42.9 203.92 42.79 C 203.86 42.71 203.84 42.6 203.86 42.46 C 203.87 42.32 203.92 42.22 204.01 42.17 C 205.54 41.31 207.16 40.66 208.86 40.23 C 210.57 39.8 212.3 39.58 214.05 39.58 C 215.8 39.58 217.51 39.79 219.17 40.2 C 220.84 40.62 222.45 41.24 224 42.08 C 224.17 42.16 224.27 42.27 224.31 42.41 C 224.35 42.55 224.35 42.67 224.29 42.79 C 224.23 42.9 224.14 42.98 224.02 43.04 C 223.89 43.1 223.78 43.1 223.67 43.04 C 222.2 42.18 220.65 41.55 219.02 41.16 C 217.39 40.77 215.74 40.58 214.04 40.58 C 212.34 40.58 210.7 40.8 209.1 41.23 C 207.5 41.66 205.94 42.26 204.41 43.04 Z M 209.92 71.96 C 208.36 70.29 207.14 68.55 206.25 66.73 C 205.36 64.91 204.92 62.82 204.92 60.46 C 204.92 58.1 205.82 55.91 207.61 54.23 C 209.4 52.55 211.55 51.71 214.05 51.71 C 216.55 51.71 218.7 52.55 220.49 54.23 C 222.28 55.91 223.18 57.99 223.18 60.46 C 223.18 60.6 223.14 60.72 223.06 60.81 C 222.98 60.91 222.85 60.96 222.68 60.96 C 222.6 60.96 222.49 60.91 222.37 60.81 C 222.25 60.71 222.18 60.6 222.18 60.46 C 222.18 58.27 221.38 56.43 219.78 54.94 C 218.18 53.45 216.27 52.71 214.05 52.71 C 211.83 52.71 209.92 53.45 208.32 54.94 C 206.72 56.43 205.92 58.27 205.92 60.46 C 205.92 62.77 206.32 64.72 207.13 66.34 C 207.94 67.95 209.1 69.58 210.63 71.22 C 210.77 71.39 210.82 71.53 210.78 71.66 C 210.74 71.78 210.69 71.89 210.63 71.97 C 210.55 72.05 210.44 72.1 210.3 72.12 C 210.16 72.13 210.04 72.09 209.92 71.97 Z M 222.34 68.96 C 219.95 68.96 217.88 68.15 216.11 66.54 C 214.35 64.93 213.46 62.9 213.46 60.46 C 213.46 60.32 213.5 60.2 213.58 60.11 C 213.66 60.01 213.79 59.96 213.96 59.96 C 214.1 59.96 214.22 60.01 214.31 60.11 C 214.41 60.21 214.46 60.33 214.46 60.46 C 214.46 62.65 215.24 64.45 216.81 65.86 C 218.38 67.26 220.22 67.96 222.33 67.96 C 222.64 67.96 222.95 67.95 223.27 67.92 C 223.59 67.89 223.9 67.85 224.21 67.8 C 224.32 67.8 224.42 67.83 224.5 67.88 C 224.58 67.93 224.64 68.03 224.67 68.17 C 224.73 68.31 224.71 68.43 224.63 68.52 C 224.55 68.62 224.42 68.68 224.25 68.71 C 224 68.79 223.7 68.86 223.35 68.9 C 223 68.94 222.66 68.96 222.33 68.96 Z"
+        android:fillColor="#1f1f1f"
+        android:strokeWidth="1"/>
+    <path
+        android:name="path_2"
+        android:pathData="M 134 72 L 134 60 C 134 46.75 144.75 36 158 36 C 171.25 36 182 46.75 182 60 L 182 72 M 124 72 L 192 72 C 195.712 72 199.275 73.476 201.899 76.101 C 204.524 78.725 206 82.288 206 86 L 206 146 C 206 149.712 204.524 153.275 201.899 155.899 C 199.275 158.524 195.712 160 192 160 L 124 160 C 120.288 160 116.725 158.524 114.101 155.899 C 111.476 153.275 110 149.712 110 146 L 110 86 C 110 82.288 111.476 78.725 114.101 76.101 C 116.725 73.476 120.288 72 124 72"
+        android:strokeColor="#1f1f1f"
+        android:strokeWidth="1"
+        android:strokeLineCap="round"/>
+    <path
+        android:name="path_3"
+        android:pathData="M 155.67 114.83 C 153.22 114.83 151.15 113.98 149.46 112.29 C 147.77 110.6 146.92 108.53 146.92 106.08 C 146.92 103.63 147.77 101.56 149.46 99.87 C 151.15 98.18 153.22 97.33 155.67 97.33 C 158.12 97.33 160.19 98.18 161.88 99.87 C 163.57 101.56 164.42 103.63 164.42 106.08 C 164.42 108.53 163.57 110.6 161.88 112.29 C 160.19 113.98 158.12 114.83 155.67 114.83 Z M 172.59 137 L 169.67 133.5 L 169.67 124.92 C 168.27 124.38 167.14 123.51 166.29 122.32 C 165.44 121.13 165.01 119.8 165.01 118.32 C 165.01 116.38 165.69 114.72 167.05 113.36 C 168.41 112 170.06 111.32 172.01 111.32 C 173.96 111.32 175.61 112 176.97 113.36 C 178.33 114.72 179.01 116.37 179.01 118.32 C 179.01 119.8 178.58 121.13 177.73 122.32 C 176.88 123.51 175.75 124.37 174.35 124.92 L 174.35 125.33 L 176.68 127.66 L 174.35 129.99 L 176.68 132.32 L 172.6 136.99 Z M 172.01 121.83 C 172.98 121.83 173.81 121.49 174.49 120.81 C 175.17 120.13 175.51 119.3 175.51 118.33 C 175.51 117.36 175.17 116.53 174.49 115.85 C 173.81 115.17 172.98 114.83 172.01 114.83 C 171.04 114.83 170.21 115.17 169.53 115.85 C 168.85 116.53 168.51 117.36 168.51 118.33 C 168.51 119.3 168.85 120.13 169.53 120.81 C 170.21 121.49 171.04 121.83 172.01 121.83 Z M 160.46 119.85 C 160.65 121.44 161.15 122.9 161.95 124.23 C 162.75 125.55 163.77 126.7 165.01 127.67 L 165.01 134.67 L 137.01 134.67 L 137.01 129.13 C 137.01 127.81 137.38 126.6 138.12 125.51 C 138.86 124.42 139.81 123.59 140.98 123 C 143.27 121.83 145.66 120.96 148.13 120.38 C 150.6 119.8 153.12 119.5 155.68 119.5 C 156.46 119.5 157.26 119.53 158.07 119.59 C 158.89 119.65 159.68 119.74 160.46 119.85 Z"
+        android:fillColor="#1f1f1f"
+        android:strokeWidth="1"/>
+    <path
+        android:name="path_4"
+        android:pathData="M 63.481 27.427 L 102.118 17.075 C 102.457 16.984 102.815 16.984 103.154 17.075 C 103.493 17.166 103.802 17.344 104.05 17.592 C 104.298 17.841 104.477 18.15 104.568 18.489 L 110.779 41.671 C 110.87 42.01 110.87 42.367 110.779 42.706 C 110.689 43.045 110.51 43.355 110.262 43.603 C 110.014 43.851 109.704 44.03 109.365 44.121 L 70.728 54.473 C 70.216 54.611 69.67 54.539 69.211 54.274 C 68.751 54.008 68.416 53.571 68.279 53.059 L 62.067 29.877 C 61.976 29.538 61.976 29.181 62.067 28.842 C 62.158 28.503 62.336 28.193 62.585 27.945 C 62.833 27.697 63.142 27.518 63.481 27.427"
+        android:strokeColor="#e1e3e1"
+        android:strokeWidth="1"
+        android:strokeLineCap="round"
+        android:strokeMiterLimit="10"/>
+    <path
+        android:name="path_5"
+        android:pathData="M 86.189 50.327 L 93.917 48.256 L 94.952 52.12 L 87.225 54.191 Z"
+        android:fillColor="#e1e3e1"
+        android:strokeWidth="1"/>
+    <path
+        android:name="path_6"
+        android:pathData="M 83.36 55.23 L 98.81 51.08"
+        android:strokeColor="#e1e3e1"
+        android:strokeWidth="1"
+        android:strokeLineCap="round"
+        android:strokeMiterLimit="10"/>
+    <path
+        android:name="path_7"
+        android:pathData="M 90.9 37.2 L 88.3 35.7 L 89.4 33.8 C 89.59 33.48 89.37 33.07 89 33.05 L 81.88 32.62 C 81.47 32.6 81.21 33.05 81.43 33.39 L 85.36 39.34 C 85.56 39.65 86.02 39.64 86.21 39.31 L 87.31 37.41 L 89.91 38.91 C 90.39 39.19 91 39.02 91.28 38.54 C 91.56 38.06 91.39 37.45 90.91 37.17 Z"
+        android:fillColor="#e1e3e1"
+        android:strokeWidth="1"/>
+    <path
+        android:name="path_8"
+        android:pathData="M 86 160 L 222 160"
+        android:strokeColor="#1f1f1f"
+        android:strokeWidth="1"
+        android:strokeLineCap="round"/>
+    <group android:name="group">
+        <path
+            android:name="path_9"
+            android:pathData="M 98 80 C 92.088 80 86.325 81.872 81.542 85.348 C 76.759 88.823 73.197 93.725 71.37 99.348 C 69.543 104.97 69.543 111.03 71.37 116.652 C 73.197 122.275 76.759 127.177 81.542 130.652 C 86.325 134.128 92.088 136 98 136 C 103.912 136 109.675 134.128 114.458 130.652 C 119.241 127.177 122.803 122.275 124.63 116.652 C 126.457 111.03 126.457 104.97 124.63 99.348 C 122.803 93.725 119.241 88.823 114.458 85.348 C 109.675 81.872 103.912 80 98 80 Z"
+            android:fillColor="#6dd48b"
+            android:strokeWidth="1"/>
+        <path
+            android:name="path_10"
+            android:pathData="M 98 90.5 C 93.36 90.5 88.906 92.345 85.626 95.626 C 82.345 98.906 80.5 103.36 80.5 108 C 80.5 112.64 82.345 117.094 85.626 120.374 C 88.906 123.655 93.36 125.5 98 125.5 C 102.64 125.5 107.094 123.655 110.374 120.374 C 113.655 117.094 115.5 112.64 115.5 108 C 115.5 103.36 113.655 98.906 110.374 95.626 C 107.094 92.345 102.64 90.5 98 90.5 Z"
+            android:strokeColor="#1f1f1f"
+            android:strokeWidth="1"
+            android:strokeMiterLimit="10"/>
+        <path
+            android:name="path_11"
+            android:pathData="M 92.31 104.5 C 92.31 105.22 91.72 105.81 91 105.81 C 90.28 105.81 89.69 105.22 89.69 104.5 C 89.69 103.78 90.28 103.19 91 103.19 C 91.72 103.19 92.31 103.78 92.31 104.5 Z M 106.31 104.5 C 106.31 105.22 105.72 105.81 105 105.81 C 104.28 105.81 103.69 105.22 103.69 104.5 C 103.69 103.78 104.28 103.19 105 103.19 C 105.72 103.19 106.31 103.78 106.31 104.5 Z"
+            android:fillColor="#1f1f1f"
+            android:strokeWidth="1"/>
+        <path
+            android:name="path_12"
+            android:pathData="M 98.88 107.12 L 98.88 110.62 L 96.61 110.62 M 100.11 116.16 C 98.94 117.33 97.06 117.33 95.89 116.16 C 95.29 115.56 95 114.76 95.02 113.97"
+            android:strokeColor="#1f1f1f"
+            android:strokeWidth="1"
+            android:strokeMiterLimit="10"/>
+    </group>
+    <group android:name="group_2">
+        <path
+            android:name="path_13"
+            android:pathData="M 264.23 104 L 296.23 104"
+            android:strokeColor="#e1e3e1"
+            android:strokeWidth="1"
+            android:strokeLineCap="round"
+            android:strokeMiterLimit="10"/>
+        <path
+            android:name="path_14"
+            android:pathData="M 273.16 81.21 C 272.82 80.19 271.87 79.5 270.79 79.5 L 243.01 79.5 C 242.2 79.5 241.46 79.88 240.98 80.54 C 240.5 81.2 240.38 82.02 240.64 82.79 L 247.31 102.79 C 247.65 103.81 248.6 104.5 249.68 104.5 L 280.93 104.5 L 273.17 81.21 Z M 248.25 87.93 C 247.45 88.14 246.57 87.45 246.28 86.39 C 245.99 85.33 246.41 84.28 247.21 84.07 C 248.01 83.86 248.89 84.55 249.18 85.61 C 249.47 86.68 249.05 87.72 248.25 87.93 Z"
+            android:fillColor="#e1e3e1"
+            android:strokeWidth="1"/>
+        <group android:name="group_1">
+            <path
+                android:name="path_15"
+                android:pathData="M 279 83 L 285 83 M 277 77 L 285 69 M 271 75 L 271 69"
+                android:strokeColor="#e1e3e1"
+                android:strokeWidth="1"
+                android:strokeLineCap="round"/>
+        </group>
+    </group>
+    <path
+        android:name="path_16"
+        android:pathData="M 58.16 105.86 C 57.83 105.28 57.29 104.87 56.64 104.69 L 45.05 101.58 C 43.72 101.22 42.34 102.02 41.99 103.35 L 35.78 126.53 C 35.61 127.17 35.7 127.85 36.03 128.43 C 36.36 129.01 36.9 129.42 37.55 129.6 L 49.14 132.71 C 49.36 132.77 49.57 132.79 49.79 132.79 C 50.89 132.79 51.91 132.05 52.21 130.94 L 58.42 107.76 C 58.59 107.11 58.5 106.44 58.17 105.86 Z M 50.65 107.75 C 50.51 108.28 49.96 108.6 49.43 108.46 C 48.9 108.32 48.58 107.77 48.72 107.24 C 48.86 106.71 49.41 106.39 49.94 106.53 C 50.47 106.67 50.79 107.22 50.65 107.75 Z"
+        android:fillColor="#e1e3e1"
+        android:strokeWidth="1"/>
+    <path
+        android:name="path_17"
+        android:pathData="M 230 160 L 246 160"
+        android:strokeColor="#1f1f1f"
+        android:strokeWidth="1"
+        android:strokeLineCap="round"/>
+    <path
+        android:name="path_18"
+        android:pathData="M 204.63 80 C 202.38 75.27 197.58 72 192 72 L 190 72 L 190 74 C 190 77.31 192.69 80 196 80 L 204.63 80 Z"
+        android:fillColor="#1f1f1f"
+        android:strokeWidth="1"/>
+    <group android:name="group_4">
+        <path
+            android:name="path_19"
+            android:pathData="M 211.37 102.29 C 210.53 102.82 209.46 102.82 208.63 102.29 C 202.93 98.67 195.29 99.35 190.31 104.32 C 185.34 109.29 184.67 116.93 188.28 122.63 C 188.81 123.46 188.81 124.54 188.28 125.38 C 184.66 131.08 185.34 138.72 190.31 143.69 C 195.29 148.66 202.93 149.34 208.63 145.72 C 209.46 145.19 210.54 145.19 211.37 145.72 C 217.07 149.34 224.71 148.66 229.68 143.69 C 234.66 138.72 235.33 131.08 231.71 125.38 C 231.18 124.55 231.18 123.47 231.71 122.63 C 235.33 116.93 234.65 109.29 229.68 104.32 C 224.71 99.35 217.07 98.67 211.37 102.29 Z"
+            android:fillColor="#0b57cf"
+            android:strokeWidth="1"
+            android:fillType="evenOdd"/>
+        <group android:name="group_3">
+            <path
+                android:name="path_20"
+                android:pathData="M 198 110 C 197.47 110 196.961 110.211 196.586 110.586 C 196.211 110.961 196 111.47 196 112 C 196 112.53 196.211 113.039 196.586 113.414 C 196.961 113.789 197.47 114 198 114 C 198.53 114 199.039 113.789 199.414 113.414 C 199.789 113.039 200 112.53 200 112 C 200 111.47 199.789 110.961 199.414 110.586 C 199.039 110.211 198.53 110 198 110 Z M 210 110 C 209.47 110 208.961 110.211 208.586 110.586 C 208.211 110.961 208 111.47 208 112 C 208 112.53 208.211 113.039 208.586 113.414 C 208.961 113.789 209.47 114 210 114 C 210.53 114 211.039 113.789 211.414 113.414 C 211.789 113.039 212 112.53 212 112 C 212 111.47 211.789 110.961 211.414 110.586 C 211.039 110.211 210.53 110 210 110 Z M 222 110 C 221.47 110 220.961 110.211 220.586 110.586 C 220.211 110.961 220 111.47 220 112 C 220 112.53 220.211 113.039 220.586 113.414 C 220.961 113.789 221.47 114 222 114 C 222.53 114 223.039 113.789 223.414 113.414 C 223.789 113.039 224 112.53 224 112 C 224 111.47 223.789 110.961 223.414 110.586 C 223.039 110.211 222.53 110 222 110 Z M 198 122 C 197.47 122 196.961 122.211 196.586 122.586 C 196.211 122.961 196 123.47 196 124 C 196 124.53 196.211 125.039 196.586 125.414 C 196.961 125.789 197.47 126 198 126 C 198.53 126 199.039 125.789 199.414 125.414 C 199.789 125.039 200 124.53 200 124 C 200 123.47 199.789 122.961 199.414 122.586 C 199.039 122.211 198.53 122 198 122 Z M 210 122 C 209.47 122 208.961 122.211 208.586 122.586 C 208.211 122.961 208 123.47 208 124 C 208 124.53 208.211 125.039 208.586 125.414 C 208.961 125.789 209.47 126 210 126 C 210.53 126 211.039 125.789 211.414 125.414 C 211.789 125.039 212 124.53 212 124 C 212 123.47 211.789 122.961 211.414 122.586 C 211.039 122.211 210.53 122 210 122 Z M 222 122 C 221.47 122 220.961 122.211 220.586 122.586 C 220.211 122.961 220 123.47 220 124 C 220 124.53 220.211 125.039 220.586 125.414 C 220.961 125.789 221.47 126 222 126 C 222.53 126 223.039 125.789 223.414 125.414 C 223.789 125.039 224 124.53 224 124 C 224 123.47 223.789 122.961 223.414 122.586 C 223.039 122.211 222.53 122 222 122 Z M 198 134 C 197.47 134 196.961 134.211 196.586 134.586 C 196.211 134.961 196 135.47 196 136 C 196 136.53 196.211 137.039 196.586 137.414 C 196.961 137.789 197.47 138 198 138 C 198.53 138 199.039 137.789 199.414 137.414 C 199.789 137.039 200 136.53 200 136 C 200 135.47 199.789 134.961 199.414 134.586 C 199.039 134.211 198.53 134 198 134 Z M 210 134 C 209.47 134 208.961 134.211 208.586 134.586 C 208.211 134.961 208 135.47 208 136 C 208 136.53 208.211 137.039 208.586 137.414 C 208.961 137.789 209.47 138 210 138 C 210.53 138 211.039 137.789 211.414 137.414 C 211.789 137.039 212 136.53 212 136 C 212 135.47 211.789 134.961 211.414 134.586 C 211.039 134.211 210.53 134 210 134 Z M 222 134 C 221.47 134 220.961 134.211 220.586 134.586 C 220.211 134.961 220 135.47 220 136 C 220 136.53 220.211 137.039 220.586 137.414 C 220.961 137.789 221.47 138 222 138 C 222.53 138 223.039 137.789 223.414 137.414 C 223.789 137.039 224 136.53 224 136 C 224 135.47 223.789 134.961 223.414 134.586 C 223.039 134.211 222.53 134 222 134 Z"
+                android:fillColor="#ffffff"
+                android:strokeWidth="1"/>
+        </group>
+        <path
+            android:name="path_21"
+            android:pathData="M 222 112 L 198 136 L 222 136"
+            android:strokeColor="#ffffff"
+            android:strokeWidth="1"
+            android:strokeLineCap="round"/>
+    </group>
+</vector>
diff --git a/packages/CredentialManager/res/drawable/ic_passkeys_onboarding_dark.xml b/packages/CredentialManager/res/drawable/ic_passkeys_onboarding_dark.xml
new file mode 100644
index 0000000..53933cb
--- /dev/null
+++ b/packages/CredentialManager/res/drawable/ic_passkeys_onboarding_dark.xml
@@ -0,0 +1,158 @@
+<!--
+  ~ 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.
+  -->
+
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:name="vector"
+    android:width="316dp"
+    android:height="168dp"
+    android:viewportWidth="316"
+    android:viewportHeight="168">
+    <path
+        android:name="path"
+        android:pathData="M 238 56 C 238 42.75 227.26 32 214 32 C 200.75 32 190 42.75 190 56 L 190 74 C 190 77.31 192.69 80 196 80 L 214 80 C 227.26 80 238 69.26 238 56 Z"
+        android:fillColor="#f09d00"
+        android:strokeWidth="1"/>
+    <path
+        android:name="path_1"
+        android:pathData="M 199.42 51.71 C 199.34 51.65 199.29 51.56 199.27 51.44 C 199.26 51.31 199.27 51.21 199.33 51.13 C 201.08 48.8 203.23 46.98 205.79 45.67 C 208.35 44.36 211.07 43.71 213.96 43.71 C 216.85 43.71 219.64 44.34 222.25 45.61 C 224.86 46.88 227.03 48.69 228.75 51.05 C 228.86 51.19 228.9 51.32 228.85 51.45 C 228.81 51.57 228.72 51.68 228.58 51.76 C 228.5 51.82 228.38 51.84 228.23 51.84 C 228.08 51.84 227.96 51.76 227.88 51.59 C 226.27 49.34 224.23 47.63 221.78 46.47 C 219.32 45.3 216.72 44.72 213.97 44.72 C 211.22 44.72 208.68 45.32 206.26 46.51 C 203.84 47.7 201.83 49.4 200.22 51.59 C 200.08 51.76 199.94 51.84 199.8 51.84 C 199.66 51.84 199.54 51.8 199.42 51.72 Z M 218.75 72.42 C 216 71.7 213.72 70.28 211.9 68.17 C 210.08 66.06 209.17 63.49 209.17 60.46 C 209.17 59.13 209.65 58.02 210.61 57.15 C 211.57 56.27 212.71 55.84 214.05 55.84 C 215.39 55.84 216.48 56.28 217.43 57.15 C 218.37 58.02 218.85 59.13 218.85 60.46 C 218.85 61.49 219.23 62.34 220 63.02 C 220.76 63.7 221.66 64.04 222.69 64.04 C 223.72 64.04 224.64 63.69 225.38 63 C 226.12 62.31 226.48 61.46 226.48 60.46 C 226.48 57.13 225.25 54.32 222.79 52.04 C 220.33 49.76 217.39 48.62 213.98 48.62 C 210.57 48.62 207.63 49.76 205.19 52.04 C 202.75 54.32 201.52 57.12 201.52 60.46 C 201.52 61.13 201.61 61.97 201.79 63 C 201.97 64.03 202.28 65.22 202.73 66.58 C 202.79 66.72 202.79 66.84 202.75 66.93 C 202.71 67.03 202.6 67.12 202.44 67.2 C 202.3 67.28 202.17 67.3 202.04 67.24 C 201.91 67.18 201.82 67.09 201.77 66.95 C 201.35 65.78 201.04 64.68 200.83 63.64 C 200.62 62.6 200.52 61.54 200.52 60.45 C 200.52 56.81 201.85 53.76 204.5 51.3 C 207.15 48.84 210.31 47.61 213.98 47.61 C 217.65 47.61 220.86 48.84 223.52 51.3 C 226.19 53.76 227.52 56.81 227.52 60.45 C 227.52 61.73 227.05 62.81 226.1 63.7 C 225.16 64.59 224.02 65.03 222.68 65.03 C 221.34 65.03 220.24 64.59 219.28 63.7 C 218.32 62.81 217.84 61.73 217.84 60.45 C 217.84 59.42 217.46 58.56 216.72 57.87 C 215.98 57.18 215.08 56.83 214.05 56.83 C 213.02 56.83 212.08 57.18 211.32 57.87 C 210.56 58.56 210.17 59.43 210.17 60.45 C 210.17 63.26 211.01 65.6 212.69 67.47 C 214.37 69.35 216.49 70.66 219.04 71.41 C 219.21 71.47 219.32 71.55 219.39 71.66 C 219.46 71.77 219.47 71.9 219.41 72.04 C 219.35 72.15 219.28 72.25 219.18 72.35 C 219.08 72.45 218.94 72.47 218.74 72.41 Z M 204.42 43.04 C 204.36 43.04 204.27 43.03 204.15 43 C 204.02 42.97 203.95 42.9 203.92 42.79 C 203.86 42.71 203.84 42.6 203.86 42.46 C 203.87 42.32 203.92 42.22 204.01 42.17 C 205.54 41.31 207.16 40.66 208.86 40.23 C 210.57 39.8 212.3 39.58 214.05 39.58 C 215.8 39.58 217.51 39.79 219.17 40.2 C 220.84 40.62 222.45 41.24 224 42.08 C 224.17 42.16 224.27 42.27 224.31 42.41 C 224.35 42.55 224.35 42.67 224.29 42.79 C 224.23 42.9 224.14 42.98 224.02 43.04 C 223.89 43.1 223.78 43.1 223.67 43.04 C 222.2 42.18 220.65 41.55 219.02 41.16 C 217.39 40.77 215.74 40.58 214.04 40.58 C 212.34 40.58 210.7 40.8 209.1 41.23 C 207.5 41.66 205.94 42.26 204.41 43.04 Z M 209.92 71.96 C 208.36 70.29 207.14 68.55 206.25 66.73 C 205.36 64.91 204.92 62.82 204.92 60.46 C 204.92 58.1 205.82 55.91 207.61 54.23 C 209.4 52.55 211.55 51.71 214.05 51.71 C 216.55 51.71 218.7 52.55 220.49 54.23 C 222.28 55.91 223.18 57.99 223.18 60.46 C 223.18 60.6 223.14 60.72 223.06 60.81 C 222.98 60.91 222.85 60.96 222.68 60.96 C 222.6 60.96 222.49 60.91 222.37 60.81 C 222.25 60.71 222.18 60.6 222.18 60.46 C 222.18 58.27 221.38 56.43 219.78 54.94 C 218.18 53.45 216.27 52.71 214.05 52.71 C 211.83 52.71 209.92 53.45 208.32 54.94 C 206.72 56.43 205.92 58.27 205.92 60.46 C 205.92 62.77 206.32 64.72 207.13 66.34 C 207.94 67.95 209.1 69.58 210.63 71.22 C 210.77 71.39 210.82 71.53 210.78 71.66 C 210.74 71.78 210.69 71.89 210.63 71.97 C 210.55 72.05 210.44 72.1 210.3 72.12 C 210.16 72.13 210.04 72.09 209.92 71.97 Z M 222.34 68.96 C 219.95 68.96 217.88 68.15 216.11 66.54 C 214.35 64.93 213.46 62.9 213.46 60.46 C 213.46 60.32 213.5 60.2 213.58 60.11 C 213.66 60.01 213.79 59.96 213.96 59.96 C 214.1 59.96 214.22 60.01 214.31 60.11 C 214.41 60.21 214.46 60.33 214.46 60.46 C 214.46 62.65 215.24 64.45 216.81 65.86 C 218.38 67.26 220.22 67.96 222.33 67.96 C 222.64 67.96 222.95 67.95 223.27 67.92 C 223.59 67.89 223.9 67.85 224.21 67.8 C 224.32 67.8 224.42 67.83 224.5 67.88 C 224.58 67.93 224.64 68.03 224.67 68.17 C 224.73 68.31 224.71 68.43 224.63 68.52 C 224.55 68.62 224.42 68.68 224.25 68.71 C 224 68.79 223.7 68.86 223.35 68.9 C 223 68.94 222.66 68.96 222.33 68.96 Z"
+        android:fillColor="#1f1f1f"
+        android:strokeWidth="1"/>
+    <path
+        android:name="path_2"
+        android:pathData="M 134 72 L 134 60 C 134 46.75 144.75 36 158 36 C 171.25 36 182 46.75 182 60 L 182 72 M 124 72 L 192 72 C 195.712 72 199.275 73.476 201.899 76.101 C 204.524 78.725 206 82.288 206 86 L 206 146 C 206 149.712 204.524 153.275 201.899 155.899 C 199.275 158.524 195.712 160 192 160 L 124 160 C 120.288 160 116.725 158.524 114.101 155.899 C 111.476 153.275 110 149.712 110 146 L 110 86 C 110 82.288 111.476 78.725 114.101 76.101 C 116.725 73.476 120.288 72 124 72"
+        android:strokeColor="#e1e3e1"
+        android:strokeWidth="1"
+        android:strokeLineCap="round"/>
+    <path
+        android:name="path_3"
+        android:pathData="M 155.67 114.83 C 153.22 114.83 151.15 113.98 149.46 112.29 C 147.77 110.6 146.92 108.53 146.92 106.08 C 146.92 103.63 147.77 101.56 149.46 99.87 C 151.15 98.18 153.22 97.33 155.67 97.33 C 158.12 97.33 160.19 98.18 161.88 99.87 C 163.57 101.56 164.42 103.63 164.42 106.08 C 164.42 108.53 163.57 110.6 161.88 112.29 C 160.19 113.98 158.12 114.83 155.67 114.83 Z M 172.59 137 L 169.67 133.5 L 169.67 124.92 C 168.27 124.38 167.14 123.51 166.29 122.32 C 165.44 121.13 165.01 119.8 165.01 118.32 C 165.01 116.38 165.69 114.72 167.05 113.36 C 168.41 112 170.06 111.32 172.01 111.32 C 173.96 111.32 175.61 112 176.97 113.36 C 178.33 114.72 179.01 116.37 179.01 118.32 C 179.01 119.8 178.58 121.13 177.73 122.32 C 176.88 123.51 175.75 124.37 174.35 124.92 L 174.35 125.33 L 176.68 127.66 L 174.35 129.99 L 176.68 132.32 L 172.6 136.99 Z M 172.01 121.83 C 172.98 121.83 173.81 121.49 174.49 120.81 C 175.17 120.13 175.51 119.3 175.51 118.33 C 175.51 117.36 175.17 116.53 174.49 115.85 C 173.81 115.17 172.98 114.83 172.01 114.83 C 171.04 114.83 170.21 115.17 169.53 115.85 C 168.85 116.53 168.51 117.36 168.51 118.33 C 168.51 119.3 168.85 120.13 169.53 120.81 C 170.21 121.49 171.04 121.83 172.01 121.83 Z M 160.46 119.85 C 160.65 121.44 161.15 122.9 161.95 124.23 C 162.75 125.55 163.77 126.7 165.01 127.67 L 165.01 134.67 L 137.01 134.67 L 137.01 129.13 C 137.01 127.81 137.38 126.6 138.12 125.51 C 138.86 124.42 139.81 123.59 140.98 123 C 143.27 121.83 145.66 120.96 148.13 120.38 C 150.6 119.8 153.12 119.5 155.68 119.5 C 156.46 119.5 157.26 119.53 158.07 119.59 C 158.89 119.65 159.68 119.74 160.46 119.85 Z"
+        android:fillColor="#e1e3e1"
+        android:strokeWidth="1"/>
+    <path
+        android:name="path_4"
+        android:pathData="M 63.481 27.427 L 102.118 17.075 C 102.457 16.984 102.815 16.984 103.154 17.075 C 103.493 17.166 103.802 17.344 104.05 17.592 C 104.298 17.841 104.477 18.15 104.568 18.489 L 110.779 41.671 C 110.87 42.01 110.87 42.367 110.779 42.706 C 110.689 43.045 110.51 43.355 110.262 43.603 C 110.014 43.851 109.704 44.03 109.365 44.121 L 70.728 54.473 C 70.216 54.611 69.67 54.539 69.211 54.274 C 68.751 54.008 68.416 53.571 68.279 53.059 L 62.067 29.877 C 61.976 29.538 61.976 29.181 62.067 28.842 C 62.158 28.503 62.336 28.193 62.585 27.945 C 62.833 27.697 63.142 27.518 63.481 27.427"
+        android:strokeColor="#444746"
+        android:strokeWidth="1"
+        android:strokeLineCap="round"
+        android:strokeMiterLimit="10"/>
+    <path
+        android:name="path_5"
+        android:pathData="M 86.189 50.327 L 93.917 48.256 L 94.952 52.12 L 87.225 54.191 Z"
+        android:fillColor="#444746"
+        android:strokeWidth="1"/>
+    <path
+        android:name="path_6"
+        android:pathData="M 83.36 55.23 L 98.81 51.08"
+        android:strokeColor="#444746"
+        android:strokeWidth="1"
+        android:strokeLineCap="round"
+        android:strokeMiterLimit="10"/>
+    <path
+        android:name="path_7"
+        android:pathData="M 90.9 37.2 L 88.3 35.7 L 89.4 33.8 C 89.59 33.48 89.37 33.07 89 33.05 L 81.88 32.62 C 81.47 32.6 81.21 33.05 81.43 33.39 L 85.36 39.34 C 85.56 39.65 86.02 39.64 86.21 39.31 L 87.31 37.41 L 89.91 38.91 C 90.39 39.19 91 39.02 91.28 38.54 C 91.56 38.06 91.39 37.45 90.91 37.17 Z"
+        android:fillColor="#444746"
+        android:strokeWidth="1"/>
+    <path
+        android:name="path_8"
+        android:pathData="M 86 160 L 222 160"
+        android:strokeColor="#e1e3e1"
+        android:strokeWidth="1"
+        android:strokeLineCap="round"/>
+    <group android:name="group">
+        <path
+            android:name="path_9"
+            android:pathData="M 98 80 C 92.088 80 86.325 81.872 81.542 85.348 C 76.759 88.823 73.197 93.725 71.37 99.348 C 69.543 104.97 69.543 111.03 71.37 116.652 C 73.197 122.275 76.759 127.177 81.542 130.652 C 86.325 134.128 92.088 136 98 136 C 103.912 136 109.675 134.128 114.458 130.652 C 119.241 127.177 122.803 122.275 124.63 116.652 C 126.457 111.03 126.457 104.97 124.63 99.348 C 122.803 93.725 119.241 88.823 114.458 85.348 C 109.675 81.872 103.912 80 98 80 Z"
+            android:fillColor="#37be5f"
+            android:strokeWidth="1"/>
+        <path
+            android:name="path_10"
+            android:pathData="M 98 90.5 C 93.36 90.5 88.906 92.345 85.626 95.626 C 82.345 98.906 80.5 103.36 80.5 108 C 80.5 112.64 82.345 117.094 85.626 120.374 C 88.906 123.655 93.36 125.5 98 125.5 C 102.64 125.5 107.094 123.655 110.374 120.374 C 113.655 117.094 115.5 112.64 115.5 108 C 115.5 103.36 113.655 98.906 110.374 95.626 C 107.094 92.345 102.64 90.5 98 90.5 Z"
+            android:strokeColor="#1f1f1f"
+            android:strokeWidth="1"
+            android:strokeMiterLimit="10"/>
+        <path
+            android:name="path_11"
+            android:pathData="M 92.31 104.5 C 92.31 105.22 91.72 105.81 91 105.81 C 90.28 105.81 89.69 105.22 89.69 104.5 C 89.69 103.78 90.28 103.19 91 103.19 C 91.72 103.19 92.31 103.78 92.31 104.5 Z M 106.31 104.5 C 106.31 105.22 105.72 105.81 105 105.81 C 104.28 105.81 103.69 105.22 103.69 104.5 C 103.69 103.78 104.28 103.19 105 103.19 C 105.72 103.19 106.31 103.78 106.31 104.5 Z"
+            android:fillColor="#1f1f1f"
+            android:strokeWidth="1"/>
+        <path
+            android:name="path_12"
+            android:pathData="M 98.88 107.12 L 98.88 110.62 L 96.61 110.62 M 100.11 116.16 C 98.94 117.33 97.06 117.33 95.89 116.16 C 95.29 115.56 95 114.76 95.02 113.97"
+            android:strokeColor="#1f1f1f"
+            android:strokeWidth="1"
+            android:strokeMiterLimit="10"/>
+    </group>
+    <group android:name="group_2">
+        <path
+            android:name="path_13"
+            android:pathData="M 264.23 104 L 296.23 104"
+            android:strokeColor="#444746"
+            android:strokeWidth="1"
+            android:strokeLineCap="round"
+            android:strokeMiterLimit="10"/>
+        <path
+            android:name="path_14"
+            android:pathData="M 273.16 81.21 C 272.82 80.19 271.87 79.5 270.79 79.5 L 243.01 79.5 C 242.2 79.5 241.46 79.88 240.98 80.54 C 240.5 81.2 240.38 82.02 240.64 82.79 L 247.31 102.79 C 247.65 103.81 248.6 104.5 249.68 104.5 L 280.93 104.5 L 273.17 81.21 Z M 248.25 87.93 C 247.45 88.14 246.57 87.45 246.28 86.39 C 245.99 85.33 246.41 84.28 247.21 84.07 C 248.01 83.86 248.89 84.55 249.18 85.61 C 249.47 86.68 249.05 87.72 248.25 87.93 Z"
+            android:fillColor="#444746"
+            android:strokeWidth="1"/>
+        <group android:name="group_1">
+            <path
+                android:name="path_15"
+                android:pathData="M 279 83 L 285 83 M 277 77 L 285 69 M 271 75 L 271 69"
+                android:strokeColor="#444746"
+                android:strokeWidth="1"
+                android:strokeLineCap="round"/>
+        </group>
+    </group>
+    <path
+        android:name="path_16"
+        android:pathData="M 58.16 105.86 C 57.83 105.28 57.29 104.87 56.64 104.69 L 45.05 101.58 C 43.72 101.22 42.34 102.02 41.99 103.35 L 35.78 126.53 C 35.61 127.17 35.7 127.85 36.03 128.43 C 36.36 129.01 36.9 129.42 37.55 129.6 L 49.14 132.71 C 49.36 132.77 49.57 132.79 49.79 132.79 C 50.89 132.79 51.91 132.05 52.21 130.94 L 58.42 107.76 C 58.59 107.11 58.5 106.44 58.17 105.86 Z M 50.65 107.75 C 50.51 108.28 49.96 108.6 49.43 108.46 C 48.9 108.32 48.58 107.77 48.72 107.24 C 48.86 106.71 49.41 106.39 49.94 106.53 C 50.47 106.67 50.79 107.22 50.65 107.75 Z"
+        android:fillColor="#444746"
+        android:strokeWidth="1"/>
+    <path
+        android:name="path_17"
+        android:pathData="M 230 160 L 246 160"
+        android:strokeColor="#e1e3e1"
+        android:strokeWidth="1"
+        android:strokeLineCap="round"/>
+    <path
+        android:name="path_18"
+        android:pathData="M 204.63 80 C 202.38 75.27 197.58 72 192 72 L 190 72 L 190 74 C 190 77.31 192.69 80 196 80 L 204.63 80 Z"
+        android:fillColor="#e1e3e1"
+        android:strokeWidth="1"/>
+    <group android:name="group_4">
+        <path
+            android:name="path_19"
+            android:pathData="M 211.37 102.29 C 210.53 102.82 209.46 102.82 208.63 102.29 C 202.93 98.67 195.29 99.35 190.31 104.32 C 185.34 109.29 184.67 116.93 188.28 122.63 C 188.81 123.46 188.81 124.54 188.28 125.38 C 184.66 131.08 185.34 138.72 190.31 143.69 C 195.29 148.66 202.93 149.34 208.63 145.72 C 209.46 145.19 210.54 145.19 211.37 145.72 C 217.07 149.34 224.71 148.66 229.68 143.69 C 234.66 138.72 235.33 131.08 231.71 125.38 C 231.18 124.55 231.18 123.47 231.71 122.63 C 235.33 116.93 234.65 109.29 229.68 104.32 C 224.71 99.35 217.07 98.67 211.37 102.29 Z"
+            android:fillColor="#7cacf8"
+            android:strokeWidth="1"
+            android:fillType="evenOdd"/>
+        <group android:name="group_3">
+            <path
+                android:name="path_20"
+                android:pathData="M 198 110 C 197.47 110 196.961 110.211 196.586 110.586 C 196.211 110.961 196 111.47 196 112 C 196 112.53 196.211 113.039 196.586 113.414 C 196.961 113.789 197.47 114 198 114 C 198.53 114 199.039 113.789 199.414 113.414 C 199.789 113.039 200 112.53 200 112 C 200 111.47 199.789 110.961 199.414 110.586 C 199.039 110.211 198.53 110 198 110 Z M 210 110 C 209.47 110 208.961 110.211 208.586 110.586 C 208.211 110.961 208 111.47 208 112 C 208 112.53 208.211 113.039 208.586 113.414 C 208.961 113.789 209.47 114 210 114 C 210.53 114 211.039 113.789 211.414 113.414 C 211.789 113.039 212 112.53 212 112 C 212 111.47 211.789 110.961 211.414 110.586 C 211.039 110.211 210.53 110 210 110 Z M 222 110 C 221.47 110 220.961 110.211 220.586 110.586 C 220.211 110.961 220 111.47 220 112 C 220 112.53 220.211 113.039 220.586 113.414 C 220.961 113.789 221.47 114 222 114 C 222.53 114 223.039 113.789 223.414 113.414 C 223.789 113.039 224 112.53 224 112 C 224 111.47 223.789 110.961 223.414 110.586 C 223.039 110.211 222.53 110 222 110 Z M 198 122 C 197.47 122 196.961 122.211 196.586 122.586 C 196.211 122.961 196 123.47 196 124 C 196 124.53 196.211 125.039 196.586 125.414 C 196.961 125.789 197.47 126 198 126 C 198.53 126 199.039 125.789 199.414 125.414 C 199.789 125.039 200 124.53 200 124 C 200 123.47 199.789 122.961 199.414 122.586 C 199.039 122.211 198.53 122 198 122 Z M 210 122 C 209.47 122 208.961 122.211 208.586 122.586 C 208.211 122.961 208 123.47 208 124 C 208 124.53 208.211 125.039 208.586 125.414 C 208.961 125.789 209.47 126 210 126 C 210.53 126 211.039 125.789 211.414 125.414 C 211.789 125.039 212 124.53 212 124 C 212 123.47 211.789 122.961 211.414 122.586 C 211.039 122.211 210.53 122 210 122 Z M 222 122 C 221.47 122 220.961 122.211 220.586 122.586 C 220.211 122.961 220 123.47 220 124 C 220 124.53 220.211 125.039 220.586 125.414 C 220.961 125.789 221.47 126 222 126 C 222.53 126 223.039 125.789 223.414 125.414 C 223.789 125.039 224 124.53 224 124 C 224 123.47 223.789 122.961 223.414 122.586 C 223.039 122.211 222.53 122 222 122 Z M 198 134 C 197.47 134 196.961 134.211 196.586 134.586 C 196.211 134.961 196 135.47 196 136 C 196 136.53 196.211 137.039 196.586 137.414 C 196.961 137.789 197.47 138 198 138 C 198.53 138 199.039 137.789 199.414 137.414 C 199.789 137.039 200 136.53 200 136 C 200 135.47 199.789 134.961 199.414 134.586 C 199.039 134.211 198.53 134 198 134 Z M 210 134 C 209.47 134 208.961 134.211 208.586 134.586 C 208.211 134.961 208 135.47 208 136 C 208 136.53 208.211 137.039 208.586 137.414 C 208.961 137.789 209.47 138 210 138 C 210.53 138 211.039 137.789 211.414 137.414 C 211.789 137.039 212 136.53 212 136 C 212 135.47 211.789 134.961 211.414 134.586 C 211.039 134.211 210.53 134 210 134 Z M 222 134 C 221.47 134 220.961 134.211 220.586 134.586 C 220.211 134.961 220 135.47 220 136 C 220 136.53 220.211 137.039 220.586 137.414 C 220.961 137.789 221.47 138 222 138 C 222.53 138 223.039 137.789 223.414 137.414 C 223.789 137.039 224 136.53 224 136 C 224 135.47 223.789 134.961 223.414 134.586 C 223.039 134.211 222.53 134 222 134 Z"
+                android:fillColor="#1f1f1f"
+                android:strokeWidth="1"/>
+        </group>
+        <path
+            android:name="path_21"
+            android:pathData="M 222 112 L 198 136 L 222 136"
+            android:strokeColor="#1f1f1f"
+            android:strokeWidth="1"
+            android:strokeLineCap="round"/>
+    </group>
+</vector>
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index 49ac482..9eda82a 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -116,8 +116,10 @@
   <string name="get_dialog_heading_for_username">For <xliff:g id="username" example="becket@gmail.com">%1$s</xliff:g></string>
   <!-- Column heading for displaying locked (that is, the user needs to first authenticate via pin, fingerprint, faceId, etc.) sign-ins. [CHAR LIMIT=80] -->
   <string name="get_dialog_heading_locked_password_managers">Locked password managers</string>
-  <!-- Explanatory sub/body text for an option entry to use a locked (that is, the user needs to first authenticate via pin, fingerprint, faceId, etc.) sign-in. [CHAR LIMIT=120] -->
-  <string name="locked_credential_entry_label_subtext">Tap to unlock</string>
+  <!-- Explanatory label for a button that takes the user to unlock a credential provider by authenticating via pin, fingerprint, faceId, etc. [CHAR LIMIT=120] -->
+  <string name="locked_credential_entry_label_subtext_tap_to_unlock">Tap to unlock</string>
+  <!-- Explanatory label for a disabled button explaining that this option isn't viable because it does not contain any available credential (e.g. password, passkey, etc.) for the user. [CHAR LIMIT=120] -->
+  <string name="locked_credential_entry_label_subtext_no_sign_in">No sign-in info</string>
   <!-- Column heading for displaying action chips for managing sign-ins from each credential provider. [CHAR LIMIT=80] -->
   <string name="get_dialog_heading_manage_sign_ins">Manage sign-ins</string>
   <!-- Column heading for displaying option to use sign-ins saved on a different device. [CHAR LIMIT=80] -->
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index 8c50271..0723c1a 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -24,6 +24,7 @@
 import android.credentials.Credential.TYPE_PASSWORD_CREDENTIAL
 import android.credentials.CredentialOption
 import android.credentials.GetCredentialRequest
+import android.credentials.ui.AuthenticationEntry
 import android.credentials.ui.Constants
 import android.credentials.ui.Entry
 import android.credentials.ui.CreateCredentialProviderData
@@ -49,7 +50,12 @@
 
 import java.time.Instant
 
-// Consider repo per screen, similar to view model?
+/**
+ * Client for interacting with Credential Manager. Also holds data inputs from it.
+ *
+ * IMPORTANT: instantiation of the object can fail if the data inputs aren't valid. Callers need
+ * to be equipped to handle this gracefully.
+ */
 class CredentialManagerRepo(
     private val context: Context,
     intent: Intent,
@@ -81,7 +87,6 @@
                     GetCredentialProviderData::class.java
                 ) ?: testGetCredentialProviderList()
             else -> {
-                // TODO: fail gracefully
                 throw IllegalStateException("Unrecognized request type: ${requestInfo.type}")
             }
         }
@@ -167,9 +172,9 @@
         )
     }
 
+    // IMPORTANT: new invocation should be mindful that this method can throw.
     private fun getCredentialInitialUiState(): GetCredentialUiState? {
         val providerEnabledList = GetFlowUtils.toProviderList(
-            // TODO: handle runtime cast error
             providerEnabledList as List<GetCredentialProviderData>, context
         )
         val requestDisplayInfo = GetFlowUtils.toRequestDisplayInfo(requestInfo, context)
@@ -179,9 +184,9 @@
         )
     }
 
+    // IMPORTANT: new invocation should be mindful that this method can throw.
     private fun getCreateProviderEnableListInitialUiState(): List<EnabledProviderInfo> {
         val providerEnabledList = CreateFlowUtils.toEnabledProviderList(
-            // Handle runtime cast error
             providerEnabledList as List<CreateCredentialProviderData>, context
         )
         return providerEnabledList
@@ -266,7 +271,7 @@
         return listOf(
             GetCredentialProviderData.Builder("io.enpass.app")
                 .setCredentialEntries(
-                    listOf<Entry>(
+                    listOf(
                         GetTestUtils.newPasswordEntry(
                             context, "key1", "subkey-1", "elisa.family@outlook.com", null,
                             Instant.ofEpochSecond(8000L)
@@ -285,9 +290,16 @@
                         ),
                     )
                 ).setAuthenticationEntries(
-            listOf<Entry>(
-                    GetTestUtils.newAuthenticationEntry(context, "key2", "subkey-1"),
-            )
+                    listOf(
+                        GetTestUtils.newAuthenticationEntry(
+                            context, "key2", "subkey-1", "locked-user1@gmail.com",
+                            AuthenticationEntry.STATUS_LOCKED
+                        ),
+                        GetTestUtils.newAuthenticationEntry(
+                            context, "key2", "subkey-2", "locked-user2@gmail.com",
+                            AuthenticationEntry.STATUS_UNLOCKED_BUT_EMPTY_MOST_RECENT
+                        ),
+                    )
                 ).setActionChips(
                     listOf(
                         GetTestUtils.newActionEntry(
@@ -315,9 +327,10 @@
                         ),
                     )
                 ).setAuthenticationEntries(
-                     listOf<Entry>(
-                    GetTestUtils.newAuthenticationEntry(context, "key2", "subkey-1"),
-                     )
+                     listOf(GetTestUtils.newAuthenticationEntry(
+                         context, "key2", "subkey-1", "foo@email.com",
+                         AuthenticationEntry.STATUS_UNLOCKED_BUT_EMPTY_LESS_RECENT
+                     ))
                 ).setActionChips(
                     listOf(
                         GetTestUtils.newActionEntry(
@@ -388,7 +401,6 @@
             CreateCredentialRequest(
                 "androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL",
                 credentialData,
-                // TODO: populate with actual data
                 /*candidateQueryData=*/ Bundle(),
                 /*isSystemProviderRequired=*/ false
             ),
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
index 5136f04..bf69ef4 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
@@ -92,13 +92,20 @@
             handleDialogState(viewModel.uiState.dialogState)
         }
 
-        if (viewModel.uiState.createCredentialUiState != null) {
+        val createCredentialUiState = viewModel.uiState.createCredentialUiState
+        val getCredentialUiState = viewModel.uiState.getCredentialUiState
+        if (createCredentialUiState != null) {
             CreateCredentialScreen(
                 viewModel = viewModel,
+                createCredentialUiState = createCredentialUiState,
                 providerActivityLauncher = launcher
             )
-        } else if (viewModel.uiState.getCredentialUiState != null) {
-            GetCredentialScreen(viewModel = viewModel, providerActivityLauncher = launcher)
+        } else if (getCredentialUiState != null) {
+            GetCredentialScreen(
+                viewModel = viewModel,
+                getCredentialUiState = getCredentialUiState,
+                providerActivityLauncher = launcher
+            )
         } else {
             Log.d(Constants.LOG_TAG, "UI wasn't able to render neither get nor create flow")
             reportInstantiationErrorAndFinishActivity(credManRepo)
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
index 6bf1513..30b4b86 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
@@ -255,14 +255,14 @@
     }
 
     fun createFlowOnEntrySelectedFromFirstUseScreen(activeEntry: ActiveEntry) {
+        val providerId = activeEntry.activeProvider.id
+        createFlowOnDefaultChanged(providerId)
         uiState = uiState.copy(
             createCredentialUiState = uiState.createCredentialUiState?.copy(
                 currentScreenState = CreateScreenState.CREATION_OPTION_SELECTION,
                 activeEntry = activeEntry
             )
         )
-        val providerId = uiState.createCredentialUiState?.activeEntry?.activeProvider?.id
-        createFlowOnDefaultChanged(providerId)
     }
 
     fun createFlowOnDisabledProvidersSelected() {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index 167b956..50036e8 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -17,10 +17,12 @@
 package com.android.credentialmanager
 
 import android.app.slice.Slice
+import android.app.slice.SliceItem
 import android.content.ComponentName
 import android.content.Context
 import android.content.pm.PackageManager
 import android.credentials.Credential.TYPE_PASSWORD_CREDENTIAL
+import android.credentials.ui.AuthenticationEntry
 import android.credentials.ui.CreateCredentialProviderData
 import android.credentials.ui.DisabledProviderData
 import android.credentials.ui.Entry
@@ -176,7 +178,9 @@
         }
 
 
-        /* From service data structure to UI credential entry list representation. */
+        /**
+         * Note: caller required handle empty list due to parsing error.
+         */
         private fun getCredentialOptionInfoList(
             providerId: String,
             credentialEntries: List<Entry>,
@@ -255,31 +259,44 @@
             }
         }
 
+        /**
+         * Note: caller required handle empty list due to parsing error.
+         */
         private fun getAuthenticationEntryList(
             providerId: String,
             providerDisplayName: String,
             providerIcon: Drawable,
-            authEntryList: List<Entry>,
+            authEntryList: List<AuthenticationEntry>,
         ): List<AuthenticationEntryInfo> {
             val result: MutableList<AuthenticationEntryInfo> = mutableListOf()
-            authEntryList.forEach {
+            authEntryList.forEach { entry ->
                 val structuredAuthEntry =
-                    AuthenticationAction.fromSlice(it.slice) ?: return@forEach
+                    AuthenticationAction.fromSlice(entry.slice) ?: return@forEach
+
+                // TODO: replace with official jetpack code.
+                val titleItem: SliceItem? = entry.slice.items.firstOrNull {
+                    it.hasHint(
+                        "androidx.credentials.provider.authenticationAction.SLICE_HINT_TITLE")
+                }
+                val title: String = titleItem?.text?.toString() ?: providerDisplayName
+
                 result.add(AuthenticationEntryInfo(
                     providerId = providerId,
-                    entryKey = it.key,
-                    entrySubkey = it.subkey,
+                    entryKey = entry.key,
+                    entrySubkey = entry.subkey,
                     pendingIntent = structuredAuthEntry.pendingIntent,
-                    fillInIntent = it.frameworkExtrasIntent,
-                    title = providerDisplayName,
+                    fillInIntent = entry.frameworkExtrasIntent,
+                    title = title,
                     icon = providerIcon,
+                    isUnlockedAndEmpty = entry.status != AuthenticationEntry.STATUS_LOCKED,
+                    isLastUnlocked =
+                    entry.status == AuthenticationEntry.STATUS_UNLOCKED_BUT_EMPTY_MOST_RECENT
                 ))
             }
             return result
         }
 
         private fun getRemoteEntry(providerId: String, remoteEntry: Entry?): RemoteEntryInfo? {
-            // TODO: should also call fromSlice after getting the official jetpack code.
             if (remoteEntry == null) {
                 return null
             }
@@ -294,6 +311,9 @@
             )
         }
 
+        /**
+         * Note: caller required handle empty list due to parsing error.
+         */
         private fun getActionEntryList(
             providerId: String,
             actionEntries: List<Entry>,
@@ -321,7 +341,9 @@
 
 class CreateFlowUtils {
     companion object {
-        // Returns the list (potentially empty) of enabled provider.
+        /**
+         * Note: caller required handle empty list due to parsing error.
+         */
         fun toEnabledProviderList(
             providerDataList: List<CreateCredentialProviderData>,
             context: Context,
@@ -346,7 +368,9 @@
             return providerList
         }
 
-        // Returns the list (potentially empty) of disabled provider.
+        /**
+         * Note: caller required handle empty list due to parsing error.
+         */
         fun toDisabledProviderList(
             providerDataList: List<DisabledProviderData>?,
             context: Context,
@@ -532,6 +556,9 @@
             } else null
         }
 
+        /**
+         * Note: caller required handle empty list due to parsing error.
+         */
         private fun toCreationOptionInfoList(
             providerId: String,
             creationEntries: List<Entry>,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/TestUtils.kt b/packages/CredentialManager/src/com/android/credentialmanager/TestUtils.kt
index e3bbaeb..eb3d188 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/TestUtils.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/TestUtils.kt
@@ -22,11 +22,13 @@
 import android.content.Context
 import android.content.Intent
 import android.credentials.Credential.TYPE_PASSWORD_CREDENTIAL
+import android.credentials.ui.AuthenticationEntry
 import android.credentials.ui.Entry
-import android.graphics.drawable.Icon
 import android.net.Uri
 import android.provider.Settings
 import androidx.credentials.provider.CreateEntry
+import androidx.credentials.provider.PasswordCredentialEntry
+import androidx.credentials.provider.PublicKeyCredentialEntry
 
 import java.time.Instant
 
@@ -37,7 +39,9 @@
             context: Context,
             key: String,
             subkey: String,
-        ): Entry {
+            title: String,
+            status: Int
+        ): AuthenticationEntry {
             val slice = Slice.Builder(
                 Uri.EMPTY, SliceSpec("AuthenticationAction", 0)
             )
@@ -52,10 +56,16 @@
                     .build(),
                 /*subType=*/null
             )
-            return Entry(
+            slice.addText(
+                title,
+                null,
+                listOf("androidx.credentials.provider.authenticationAction.SLICE_HINT_TITLE")
+            )
+            return AuthenticationEntry(
                 key,
                 subkey,
-                slice.build()
+                slice.build(),
+                status
             )
         }
 
@@ -94,23 +104,6 @@
             )
         }
 
-        private const val SLICE_HINT_TYPE_DISPLAY_NAME =
-            "androidx.credentials.provider.passwordCredentialEntry.SLICE_HINT_TYPE_DISPLAY_NAME"
-        private const val SLICE_HINT_TITLE =
-            "androidx.credentials.provider.passwordCredentialEntry.SLICE_HINT_USER_NAME"
-        private const val SLICE_HINT_SUBTITLE =
-            "androidx.credentials.provider.passwordCredentialEntry.SLICE_HINT_TYPE_DISPLAY_NAME"
-        private const val SLICE_HINT_LAST_USED_TIME_MILLIS =
-            "androidx.credentials.provider.passwordCredentialEntry.SLICE_HINT_LAST_USED_TIME_MILLIS"
-        private const val SLICE_HINT_ICON =
-            "androidx.credentials.provider.passwordCredentialEntry.SLICE_HINT_PROFILE_ICON"
-        private const val SLICE_HINT_PENDING_INTENT =
-            "androidx.credentials.provider.passwordCredentialEntry.SLICE_HINT_PENDING_INTENT"
-        private const val SLICE_HINT_AUTO_ALLOWED =
-            "androidx.credentials.provider.passwordCredentialEntry.SLICE_HINT_AUTO_ALLOWED"
-        private const val AUTO_SELECT_TRUE_STRING = "true"
-        private const val AUTO_SELECT_FALSE_STRING = "false"
-
         internal fun newPasswordEntry(
             context: Context,
             key: String,
@@ -125,90 +118,13 @@
             val pendingIntent = PendingIntent.getActivity(
                 context, 1,
                 intent, (PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
-                    or PendingIntent.FLAG_ONE_SHOT)
+                or PendingIntent.FLAG_ONE_SHOT)
             )
-            return Entry(
-                key,
-                subkey,
-                toPasswordSlice(userName, userDisplayName, pendingIntent, lastUsedTime),
-                Intent()
-            )
+            val passwordEntry = PasswordCredentialEntry.Builder(context, userName, pendingIntent)
+                .setDisplayName(userDisplayName).setLastUsedTime(lastUsedTime).build()
+            return Entry(key, subkey, passwordEntry.slice, Intent())
         }
 
-        private fun toPasswordSlice(
-            title: CharSequence,
-            subTitle: CharSequence?,
-            pendingIntent: PendingIntent,
-            lastUsedTime: Instant?,
-            icon: Icon? = null,
-            isAutoSelectAllowed: Boolean = true
-        ): Slice {
-            val type = TYPE_PASSWORD_CREDENTIAL
-            val autoSelectAllowed = if (isAutoSelectAllowed) {
-                AUTO_SELECT_TRUE_STRING
-            } else {
-                AUTO_SELECT_FALSE_STRING
-            }
-            val sliceBuilder = Slice.Builder(
-                Uri.EMPTY, SliceSpec(
-                    type, 1
-                )
-            )
-                .addText(
-                    "Password", /*subType=*/null,
-                    listOf(SLICE_HINT_TYPE_DISPLAY_NAME)
-                )
-                .addText(
-                    title, /*subType=*/null,
-                    listOf(SLICE_HINT_TITLE)
-                )
-                .addText(
-                    subTitle, /*subType=*/null,
-                    listOf(SLICE_HINT_SUBTITLE)
-                )
-                .addText(
-                    autoSelectAllowed, /*subType=*/null,
-                    listOf(SLICE_HINT_AUTO_ALLOWED)
-                )
-            if (lastUsedTime != null) {
-                sliceBuilder.addLong(
-                    lastUsedTime.toEpochMilli(),
-                    /*subType=*/null,
-                    listOf(SLICE_HINT_LAST_USED_TIME_MILLIS)
-                )
-            }
-            if (icon != null) {
-                sliceBuilder.addIcon(
-                    icon, /*subType=*/null,
-                    listOf(SLICE_HINT_ICON)
-                )
-            }
-            sliceBuilder.addAction(
-                pendingIntent,
-                Slice.Builder(sliceBuilder)
-                    .addHints(listOf(SLICE_HINT_PENDING_INTENT))
-                    .build(),
-                /*subType=*/null
-            )
-            return sliceBuilder.build()
-        }
-
-
-        private const val PASSKEY_SLICE_HINT_TYPE_DISPLAY_NAME =
-            "androidx.credentials.provider.publicKeyCredEntry.SLICE_HINT_TYPE_DISPLAY_NAME"
-        private const val PASSKEY_SLICE_HINT_TITLE =
-            "androidx.credentials.provider.publicKeyCredEntry.SLICE_HINT_USER_NAME"
-        private const val PASSKEY_SLICE_HINT_SUBTITLE =
-            "androidx.credentials.provider.publicKeyCredEntry.SLICE_HINT_TYPE_DISPLAY_NAME"
-        private const val PASSKEY_SLICE_HINT_LAST_USED_TIME_MILLIS =
-            "androidx.credentials.provider.publicKeyCredEntry.SLICE_HINT_LAST_USED_TIME_MILLIS"
-        private const val PASSKEY_SLICE_HINT_ICON =
-            "androidx.credentials.provider.publicKeyCredEntry.SLICE_HINT_PROFILE_ICON"
-        private const val PASSKEY_SLICE_HINT_PENDING_INTENT =
-            "androidx.credentials.provider.publicKeyCredEntry.SLICE_HINT_PENDING_INTENT"
-        private const val PASSKEY_SLICE_HINT_AUTO_ALLOWED =
-            "androidx.credentials.provider.publicKeyCredEntry.SLICE_HINT_AUTO_ALLOWED"
-
         internal fun newPasskeyEntry(
             context: Context,
             key: String,
@@ -223,72 +139,11 @@
             val pendingIntent = PendingIntent.getActivity(
                 context, 1,
                 intent, (PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
-                    or PendingIntent.FLAG_ONE_SHOT)
+                or PendingIntent.FLAG_ONE_SHOT)
             )
-            return Entry(
-                key, subkey, toPasskeySlice(
-                    userName, userDisplayName, pendingIntent, lastUsedTime
-                ),
-                Intent()
-            )
-        }
-
-        private fun toPasskeySlice(
-            title: CharSequence,
-            subTitle: CharSequence?,
-            pendingIntent: PendingIntent,
-            lastUsedTime: Instant?,
-            icon: Icon? = null,
-            isAutoSelectAllowed: Boolean = true
-        ): Slice {
-            val type = "androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL"
-            val autoSelectAllowed = if (isAutoSelectAllowed) {
-                AUTO_SELECT_TRUE_STRING
-            } else {
-                AUTO_SELECT_FALSE_STRING
-            }
-            val sliceBuilder = Slice.Builder(
-                Uri.EMPTY, SliceSpec(
-                    type, 1
-                )
-            )
-                .addText(
-                    "Passkey", /*subType=*/null,
-                    listOf(PASSKEY_SLICE_HINT_TYPE_DISPLAY_NAME)
-                )
-                .addText(
-                    title, /*subType=*/null,
-                    listOf(PASSKEY_SLICE_HINT_TITLE)
-                )
-                .addText(
-                    subTitle, /*subType=*/null,
-                    listOf(PASSKEY_SLICE_HINT_SUBTITLE)
-                )
-                .addText(
-                    autoSelectAllowed, /*subType=*/null,
-                    listOf(PASSKEY_SLICE_HINT_AUTO_ALLOWED)
-                )
-            if (lastUsedTime != null) {
-                sliceBuilder.addLong(
-                    lastUsedTime.toEpochMilli(),
-                    /*subType=*/null,
-                    listOf(PASSKEY_SLICE_HINT_LAST_USED_TIME_MILLIS)
-                )
-            }
-            if (icon != null) {
-                sliceBuilder.addIcon(
-                    icon, /*subType=*/null,
-                    listOf(PASSKEY_SLICE_HINT_ICON)
-                )
-            }
-            sliceBuilder.addAction(
-                pendingIntent,
-                Slice.Builder(sliceBuilder)
-                    .addHints(listOf(PASSKEY_SLICE_HINT_PENDING_INTENT))
-                    .build(),
-                /*subType=*/null
-            )
-            return sliceBuilder.build()
+            val passkeyEntry = PublicKeyCredentialEntry.Builder(context, userName, pendingIntent)
+                .setDisplayName(userDisplayName).setLastUsedTime(lastUsedTime).build()
+            return Entry(key, subkey, passkeyEntry.slice, Intent())
         }
     }
 }
@@ -326,7 +181,7 @@
             val pendingIntent = PendingIntent.getActivity(
                 context, 1,
                 intent, (PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
-                    or PendingIntent.FLAG_ONE_SHOT)
+                or PendingIntent.FLAG_ONE_SHOT)
             )
             val credCountMap = mutableMapOf<String, Int>()
             passwordCount?.let { credCountMap.put(TYPE_PASSWORD_CREDENTIAL, it) }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
index adb5467..a7f17c8 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
@@ -5,6 +5,7 @@
 import androidx.activity.compose.ManagedActivityResultLauncher
 import androidx.activity.result.ActivityResult
 import androidx.activity.result.IntentSenderRequest
+import androidx.compose.foundation.isSystemInDarkTheme
 import androidx.compose.foundation.Image
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Column
@@ -62,9 +63,9 @@
 @Composable
 fun CreateCredentialScreen(
     viewModel: CredentialSelectorViewModel,
+    createCredentialUiState: CreateCredentialUiState,
     providerActivityLauncher: ManagedActivityResultLauncher<IntentSenderRequest, ActivityResult>
 ) {
-    val createCredentialUiState = viewModel.uiState.createCredentialUiState ?: return
     ModalBottomSheet(
         sheetContent = {
             // Hide the sheet content as opposed to the whole bottom sheet to maintain the scrim
@@ -94,6 +95,7 @@
                             requestDisplayInfo = createCredentialUiState.requestDisplayInfo,
                             enabledProviderList = createCredentialUiState.enabledProviders,
                             providerInfo = createCredentialUiState.activeEntry?.activeProvider!!,
+                            hasDefaultProvider = createCredentialUiState.hasDefaultProvider,
                             createOptionInfo =
                             createCredentialUiState.activeEntry.activeEntryInfo
                                 as CreateOptionInfo,
@@ -164,8 +166,16 @@
 ) {
     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(R.drawable.ic_passkeys_onboarding),
+                painter = painterResource(onboardingImageResource.value),
                 contentDescription = null,
                 modifier = Modifier.align(alignment = Alignment.CenterHorizontally)
                     .padding(top = 24.dp, bottom = 12.dp).size(316.dp, 168.dp)
@@ -264,7 +274,6 @@
     }
 }
 
-@OptIn(ExperimentalMaterial3Api::class)
 @Composable
 fun ProviderSelectionCard(
     requestDisplayInfo: RequestDisplayInfo,
@@ -351,7 +360,6 @@
                 thickness = 24.dp,
                 color = Color.Transparent
             )
-            // TODO: handle the error situation that if multiple remoteInfos exists
             enabledProviderList.forEach { enabledProvider ->
                 if (enabledProvider.remoteEntry != null) {
                     Row(
@@ -363,6 +371,7 @@
                             onMoreOptionsSelected
                         )
                     }
+                    return@forEach
                 }
             }
             Divider(
@@ -463,7 +472,6 @@
                             )
                         }
                     }
-                    // TODO: handle the error situation that if multiple remoteInfos exists
                     enabledProviderList.forEach {
                         if (it.remoteEntry != null) {
                             item {
@@ -472,6 +480,7 @@
                                     onRemoteEntrySelected = onRemoteEntrySelected,
                                 )
                             }
+                            return@forEach
                         }
                     }
                 }
@@ -549,6 +558,7 @@
     onOptionSelected: (BaseEntry) -> Unit,
     onConfirm: () -> Unit,
     onMoreOptionsSelected: () -> Unit,
+    hasDefaultProvider: Boolean,
 ) {
     ContainerCard() {
         Column() {
@@ -601,7 +611,6 @@
                     onOptionSelected = onOptionSelected
                 )
             }
-            var shouldShowMoreOptionsButton = false
             var createOptionsSize = 0
             var remoteEntry: RemoteInfo? = null
             enabledProviderList.forEach { enabledProvider ->
@@ -610,8 +619,13 @@
                 }
                 createOptionsSize += enabledProvider.createOptions.size
             }
-            if (createOptionsSize > 1 || remoteEntry != null) {
-                shouldShowMoreOptionsButton = true
+            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 =
@@ -826,7 +840,6 @@
         },
         label = {
             Column() {
-                // TODO: Add the function to hide/view password when the type is create password
                 when (requestDisplayInfo.type) {
                     CredentialType.PASSKEY -> {
                         TextOnSurfaceVariant(
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index 8b311fe..438978c 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -76,9 +76,9 @@
 @Composable
 fun GetCredentialScreen(
     viewModel: CredentialSelectorViewModel,
+    getCredentialUiState: GetCredentialUiState,
     providerActivityLauncher: ManagedActivityResultLauncher<IntentSenderRequest, ActivityResult>
 ) {
-    val getCredentialUiState = viewModel.uiState.getCredentialUiState ?: return
     if (getCredentialUiState.currentScreenState != GetScreenState.REMOTE_ONLY) {
         ModalBottomSheet(
             sheetContent = {
@@ -485,7 +485,6 @@
     }
 }
 
-@OptIn(ExperimentalMaterial3Api::class)
 @Composable
 fun CredentialEntryRow(
     credentialEntryInfo: CredentialEntryInfo,
@@ -498,15 +497,13 @@
                 Image(
                     modifier = Modifier.padding(start = 10.dp).size(32.dp),
                     bitmap = credentialEntryInfo.icon.toBitmap().asImageBitmap(),
-                    // TODO: add description.
-                    contentDescription = "",
+                    contentDescription = null,
                 )
             } else {
                 Icon(
                     modifier = Modifier.padding(start = 10.dp).size(32.dp),
                     painter = painterResource(R.drawable.ic_other_sign_in),
-                    // TODO: add description.
-                    contentDescription = "",
+                    contentDescription = null,
                     tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant
                 )
             }
@@ -553,8 +550,7 @@
             Image(
                 modifier = Modifier.padding(start = 10.dp).size(32.dp),
                 bitmap = authenticationEntryInfo.icon.toBitmap().asImageBitmap(),
-                // TODO: add description.
-                contentDescription = ""
+                contentDescription = null
             )
         },
         label = {
@@ -563,23 +559,28 @@
                 modifier = Modifier.fillMaxWidth().padding(horizontal = 5.dp),
             ) {
                 Column() {
-                    // TODO: fix the text values.
                     TextOnSurfaceVariant(
                         text = authenticationEntryInfo.title,
                         style = MaterialTheme.typography.titleLarge,
                         modifier = Modifier.padding(top = 16.dp)
                     )
                     TextSecondary(
-                        text = stringResource(R.string.locked_credential_entry_label_subtext),
+                        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)
                     )
                 }
-                Icon(
-                    Icons.Outlined.Lock,
-                    null,
-                    Modifier.align(alignment = Alignment.CenterVertically).padding(end = 10.dp),
-                )
+                if (!authenticationEntryInfo.isUnlockedAndEmpty) {
+                    Icon(
+                        Icons.Outlined.Lock,
+                        null,
+                        Modifier.align(alignment = Alignment.CenterVertically).padding(end = 10.dp),
+                    )
+                }
             }
         }
     )
@@ -596,8 +597,7 @@
             Image(
                 modifier = Modifier.padding(start = 10.dp).size(24.dp),
                 bitmap = actionEntryInfo.icon.toBitmap().asImageBitmap(),
-                // TODO: add description.
-                contentDescription = ""
+                contentDescription = null,
             )
         },
         label = {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
index bca06c7..1a30295 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
@@ -91,6 +91,11 @@
     fillInIntent: Intent?,
     val title: 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.
+    val isUnlockedAndEmpty: Boolean,
+    // True if the entry was the last one unlocked. Used to show the no sign-in info snackbar.
+    val isLastUnlocked: Boolean,
 ) : BaseEntry(
     providerId,
     entryKey, entrySubkey,
diff --git a/packages/SettingsLib/Spa/gallery/build.gradle b/packages/SettingsLib/Spa/gallery/build.gradle
index 7868aff..416a403 100644
--- a/packages/SettingsLib/Spa/gallery/build.gradle
+++ b/packages/SettingsLib/Spa/gallery/build.gradle
@@ -42,11 +42,11 @@
         }
     }
     compileOptions {
-        sourceCompatibility JavaVersion.VERSION_1_8
-        targetCompatibility JavaVersion.VERSION_1_8
+        sourceCompatibility JavaVersion.VERSION_11
+        targetCompatibility JavaVersion.VERSION_11
     }
     kotlinOptions {
-        jvmTarget = '1.8'
+        jvmTarget = '11'
         freeCompilerArgs = ["-Xjvm-default=all"]
     }
     buildFeatures {
diff --git a/packages/SettingsLib/Spa/spa/build.gradle b/packages/SettingsLib/Spa/spa/build.gradle
index e035615..6f1b41c 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle
+++ b/packages/SettingsLib/Spa/spa/build.gradle
@@ -48,11 +48,11 @@
         }
     }
     compileOptions {
-        sourceCompatibility JavaVersion.VERSION_1_8
-        targetCompatibility JavaVersion.VERSION_1_8
+        sourceCompatibility JavaVersion.VERSION_11
+        targetCompatibility JavaVersion.VERSION_11
     }
     kotlinOptions {
-        jvmTarget = '1.8'
+        jvmTarget = '11'
         freeCompilerArgs = ["-Xjvm-default=all"]
     }
     buildFeatures {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
index e8b5b19..f6bb3cc 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
@@ -119,7 +119,6 @@
         actions = actions,
         colors = topAppBarColors(),
         windowInsets = TopAppBarDefaults.windowInsets,
-        maxHeightWithoutTitle = 120.dp,
         pinnedHeight = ContainerHeight,
         scrollBehavior = scrollBehavior,
     )
@@ -261,7 +260,7 @@
  * A two-rows top app bar that is designed to be called by the Large and Medium top app bar
  * composables.
  *
- * @throws [IllegalArgumentException] if the given [maxHeightWithoutTitle] is equal or smaller than
+ * @throws [IllegalArgumentException] if the given [MaxHeightWithoutTitle] is equal or smaller than
  * the [pinnedHeight]
  */
 @OptIn(ExperimentalMaterial3Api::class)
@@ -277,11 +276,10 @@
     actions: @Composable RowScope.() -> Unit,
     windowInsets: WindowInsets,
     colors: TopAppBarColors,
-    maxHeightWithoutTitle: Dp,
     pinnedHeight: Dp,
     scrollBehavior: TopAppBarScrollBehavior?
 ) {
-    if (maxHeightWithoutTitle <= pinnedHeight) {
+    if (MaxHeightWithoutTitle <= pinnedHeight) {
         throw IllegalArgumentException(
             "A TwoRowsTopAppBar max height should be greater than its pinned height"
         )
@@ -289,7 +287,7 @@
     val pinnedHeightPx: Float
     val density = LocalDensity.current
     val maxHeightPx = density.run {
-        remember { mutableStateOf((maxHeightWithoutTitle + pinnedHeight).toPx()) }
+        remember { mutableStateOf((MaxHeightWithoutTitle + DefaultTitleHeight).toPx()) }
     }
     val titleBottomPaddingPx: Int
     density.run {
@@ -380,7 +378,7 @@
                     Box(modifier = Modifier.onGloballyPositioned { coordinates ->
                         density.run {
                             maxHeightPx.value =
-                                maxHeightWithoutTitle.toPx() + coordinates.size.height.toFloat()
+                                MaxHeightWithoutTitle.toPx() + coordinates.size.height.toFloat()
                         }
                     }) { title() }
                 },
@@ -610,6 +608,8 @@
 // Medium or Large app bar.
 private val TopTitleAlphaEasing = CubicBezierEasing(.8f, 0f, .8f, .15f)
 
+private val MaxHeightWithoutTitle = 124.dp
+private val DefaultTitleHeight = 52.dp
 private val ContainerHeight = 56.dp
 private val LargeTitleBottomPadding = 28.dp
 private val TopAppBarHorizontalPadding = 4.dp
diff --git a/packages/SettingsLib/Spa/testutils/build.gradle b/packages/SettingsLib/Spa/testutils/build.gradle
index dd7058d..536829e 100644
--- a/packages/SettingsLib/Spa/testutils/build.gradle
+++ b/packages/SettingsLib/Spa/testutils/build.gradle
@@ -20,6 +20,7 @@
 }
 
 android {
+    namespace 'com.android.settingslib.spa.testutils'
     compileSdk TARGET_SDK
     buildToolsVersion = BUILD_TOOLS_VERSION
 
@@ -37,11 +38,11 @@
         }
     }
     compileOptions {
-        sourceCompatibility JavaVersion.VERSION_1_8
-        targetCompatibility JavaVersion.VERSION_1_8
+        sourceCompatibility JavaVersion.VERSION_11
+        targetCompatibility JavaVersion.VERSION_11
     }
     kotlinOptions {
-        jvmTarget = '1.8'
+        jvmTarget = '11'
         freeCompilerArgs = ["-Xjvm-default=all"]
     }
     buildFeatures {
@@ -55,7 +56,7 @@
 dependencies {
     api project(":spa")
 
-    api "androidx.arch.core:core-testing:2.1.0"
+    api "androidx.arch.core:core-testing:2.2.0-alpha01"
     api "androidx.compose.ui:ui-test-junit4:$jetpack_compose_version"
     api "com.google.truth:truth:1.1.3"
     api "org.mockito:mockito-core:2.21.0"
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppStorageSize.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppStorageSize.kt
index 037b737..1a3c0ab 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppStorageSize.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppStorageSize.kt
@@ -43,5 +43,5 @@
 private fun ApplicationInfo.calculateSizeBytes(context: Context): Long {
     val storageStatsManager = context.storageStatsManager
     val stats = storageStatsManager.queryStatsForPackage(storageUuid, packageName, userHandle)
-    return stats.codeBytes + stats.dataBytes + stats.cacheBytes
+    return stats.codeBytes + stats.dataBytes
 }
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt
index 066e28a..fcacc34 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt
@@ -75,7 +75,7 @@
             }
         }
 
-        composeTestRule.waitUntil { storageSize.value == "123 B" }
+        composeTestRule.waitUntil { storageSize.value == "120 B" }
     }
 
     companion object {
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java
index a82f070..96e875b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java
@@ -70,6 +70,8 @@
             "com.android.settings.category.ia.smart_battery_settings";
     public static final String CATEGORY_COMMUNAL_SETTINGS =
             "com.android.settings.category.ia.communal";
+    public static final String CATEGORY_MORE_SECURITY_PRIVACY_SETTINGS =
+            "com.android.settings.category.ia.more_security_privacy_settings";
 
     public static final Map<String, String> KEY_COMPAT_MAP;
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
index 8b68a09..e0588ee 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
@@ -53,6 +53,12 @@
     public final int maxChargingWattage;
     public final boolean present;
 
+    public static BatteryStatus create(Context context) {
+        final Intent batteryChangedIntent = BatteryUtils.getBatteryIntent(context);
+        return batteryChangedIntent == null
+                ? null : new BatteryStatus(batteryChangedIntent);
+    }
+
     public BatteryStatus(int status, int level, int plugged, int health,
             int maxChargingWattage, boolean present) {
         this.status = status;
@@ -87,11 +93,7 @@
         }
     }
 
-    /**
-     * Determine whether the device is plugged in (USB, power, wireless or dock).
-     *
-     * @return true if the device is plugged in.
-     */
+    /** Determine whether the device is plugged. */
     public boolean isPluggedIn() {
         return plugged == BatteryManager.BATTERY_PLUGGED_AC
                 || plugged == BatteryManager.BATTERY_PLUGGED_USB
@@ -99,30 +101,19 @@
                 || plugged == BatteryManager.BATTERY_PLUGGED_DOCK;
     }
 
-    /**
-     * Determine whether the device is plugged in (USB, power).
-     *
-     * @return true if the device is plugged in wired (as opposed to wireless)
-     */
+    /** Determine whether the device is plugged in (USB, power). */
     public boolean isPluggedInWired() {
         return plugged == BatteryManager.BATTERY_PLUGGED_AC
                 || plugged == BatteryManager.BATTERY_PLUGGED_USB;
     }
 
     /**
-     * Determine whether the device is plugged in wireless.
-     *
-     * @return true if the device is plugged in wireless
-     */
+     * Determine whether the device is plugged in wireless. */
     public boolean isPluggedInWireless() {
         return plugged == BatteryManager.BATTERY_PLUGGED_WIRELESS;
     }
 
-    /**
-     * Determine whether the device is plugged in dock.
-     *
-     * @return true if the device is plugged in dock
-     */
+    /** Determine whether the device is plugged in dock. */
     public boolean isPluggedInDock() {
         return plugged == BatteryManager.BATTERY_PLUGGED_DOCK;
     }
@@ -131,36 +122,22 @@
      * Whether or not the device is charged. Note that some devices never return 100% for
      * battery level, so this allows either battery level or status to determine if the
      * battery is charged.
-     *
-     * @return true if the device is charged
      */
     public boolean isCharged() {
         return isCharged(status, level);
     }
 
-    /**
-     * Whether battery is low and needs to be charged.
-     *
-     * @return true if battery is low
-     */
+    /** Whether battery is low and needs to be charged. */
     public boolean isBatteryLow() {
         return level < LOW_BATTERY_THRESHOLD;
     }
 
-    /**
-     * Whether battery is overheated.
-     *
-     * @return true if battery is overheated
-     */
+    /** Whether battery is overheated. */
     public boolean isOverheated() {
         return health == BATTERY_HEALTH_OVERHEAT;
     }
 
-    /**
-     * Return current chargin speed is fast, slow or normal.
-     *
-     * @return the charing speed
-     */
+    /** Return current chargin speed is fast, slow or normal. */
     public final int getChargingSpeed(Context context) {
         final int slowThreshold = context.getResources().getInteger(
                 R.integer.config_chargingSlowlyThreshold);
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryUtils.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryUtils.java
new file mode 100644
index 0000000..ad9886e
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryUtils.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.fuelgauge;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+
+public final class BatteryUtils {
+
+    /** Gets the latest sticky battery intent from the Android system. */
+    public static Intent getBatteryIntent(Context context) {
+        return context.registerReceiver(
+                /*receiver=*/ null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index 07bd9ec..1b832bf 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -106,6 +106,15 @@
         mMediaDevices.clear();
         mRouterManager.registerCallback(mExecutor, mMediaRouterCallback);
         mRouterManager.registerScanRequest();
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE
+                && !TextUtils.isEmpty(mPackageName)) {
+            RouteListingPreference routeListingPreference =
+                    mRouterManager.getRouteListingPreference(mPackageName);
+            if (routeListingPreference != null) {
+                Api34Impl.onRouteListingPreferenceUpdated(null, routeListingPreference,
+                        mPreferenceItemMap);
+            }
+        }
         refreshDevices();
     }
 
@@ -500,7 +509,8 @@
                 infos.add(transferableRoute);
             }
         }
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE
+                && !TextUtils.isEmpty(mPackageName)) {
             RouteListingPreference routeListingPreference =
                     mRouterManager.getRouteListingPreference(mPackageName);
             if (routeListingPreference != null) {
@@ -633,6 +643,7 @@
             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
                 Api34Impl.onRouteListingPreferenceUpdated(packageName, routeListingPreference,
                         mPreferenceItemMap);
+                refreshDevices();
             }
         }
     }
@@ -695,6 +706,9 @@
         @DoNotInline
         static boolean preferRouteListingOrdering(MediaRouter2Manager mediaRouter2Manager,
                 String packageName) {
+            if (TextUtils.isEmpty(packageName)) {
+                return false;
+            }
             RouteListingPreference routeListingPreference =
                     mediaRouter2Manager.getRouteListingPreference(packageName);
             return routeListingPreference != null
@@ -705,6 +719,9 @@
         @Nullable
         static ComponentName getLinkedItemComponentName(
                 MediaRouter2Manager mediaRouter2Manager, String packageName) {
+            if (TextUtils.isEmpty(packageName)) {
+                return null;
+            }
             RouteListingPreference routeListingPreference =
                     mediaRouter2Manager.getRouteListingPreference(packageName);
             return routeListingPreference == null ? null
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index a9c0f00..f1413e5 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -33,6 +33,7 @@
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY;
 
 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 android.Manifest;
@@ -107,6 +108,7 @@
 import android.util.SparseBooleanArray;
 import android.util.proto.ProtoOutputStream;
 
+import com.android.internal.accessibility.util.AccessibilityUtils;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.content.PackageMonitor;
 import com.android.internal.os.BackgroundThread;
@@ -3712,7 +3714,7 @@
         }
 
         private final class UpgradeController {
-            private static final int SETTINGS_VERSION = 213;
+            private static final int SETTINGS_VERSION = 214;
 
             private final int mUserId;
 
@@ -5646,6 +5648,27 @@
                     currentVersion = 213;
                 }
 
+                if (currentVersion == 213) {
+                    final ComponentName accessibilityMenuToMigrate =
+                            AccessibilityUtils.getAccessibilityMenuComponentToMigrate(
+                                    getContext().getPackageManager(), userId);
+                    if (accessibilityMenuToMigrate != null) {
+                        final SettingsState secureSettings = getSecureSettingsLocked(userId);
+                        final String toRemove = accessibilityMenuToMigrate.flattenToString();
+                        final String toAdd = ACCESSIBILITY_MENU_IN_SYSTEM.flattenToString();
+                        // Migrate the accessibility shortcuts and enabled state.
+                        migrateColonDelimitedStringSettingLocked(secureSettings,
+                                Secure.ACCESSIBILITY_BUTTON_TARGETS, toRemove, toAdd);
+                        migrateColonDelimitedStringSettingLocked(secureSettings,
+                                Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT, toRemove, toAdd);
+                        migrateColonDelimitedStringSettingLocked(secureSettings,
+                                Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, toRemove, toAdd);
+                        migrateColonDelimitedStringSettingLocked(secureSettings,
+                                Secure.ENABLED_ACCESSIBILITY_SERVICES, toRemove, toAdd);
+                    }
+                    currentVersion = 214;
+                }
+
                 // vXXX: Add new settings above this point.
 
                 if (currentVersion != newVersion) {
@@ -5867,6 +5890,22 @@
             return items;
         }
 
+        @GuardedBy("mLock")
+        private void migrateColonDelimitedStringSettingLocked(SettingsState settingsState,
+                String setting, String toRemove, String toAdd) {
+            final Set<String> componentNames = transformColonDelimitedStringToSet(
+                    settingsState.getSettingLocked(setting).getValue());
+            if (componentNames != null && componentNames.contains(toRemove)) {
+                componentNames.remove(toRemove);
+                componentNames.add(toAdd);
+                settingsState.insertSettingLocked(
+                        setting,
+                        TextUtils.join(":", componentNames),
+                        null /* tag */, false /* makeDefault */,
+                        SettingsState.SYSTEM_PACKAGE_NAME);
+            }
+        }
+
         private boolean isAccessibilityButtonInNavigationBarOn(SettingsState secureSettings) {
             return hasValueInA11yButtonTargets(secureSettings) && !isGestureNavigateEnabled();
         }
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 10305e5..4e18222 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -944,7 +944,7 @@
                   android:showWhenLocked="true"
                   android:showForAllUsers="true"
                   android:finishOnTaskLaunch="true"
-                  android:launchMode="singleInstance"
+                  android:lockTaskMode="if_whitelisted"
                   android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden"
                   android:visibleToInstantApps="true">
         </activity>
diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING
index 333aeba..bdd941d 100644
--- a/packages/SystemUI/TEST_MAPPING
+++ b/packages/SystemUI/TEST_MAPPING
@@ -49,13 +49,16 @@
     },
     {
       // Permission indicators
-      "name": "CtsPermission4TestCases",
+      "name": "CtsPermission3TestCases",
       "options": [
         {
           "exclude-annotation": "org.junit.Ignore"
         },
         {
           "exclude-annotation": "androidx.test.filters.FlakyTest"
+        },
+        {
+          "include-filter": "android.permission3.cts.CameraMicIndicatorsPermissionTest"
         }
       ]
     },
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/shared/model/ClockPreviewConstants.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/shared/model/ClockPreviewConstants.kt
new file mode 100644
index 0000000..6a77936
--- /dev/null
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/shared/model/ClockPreviewConstants.kt
@@ -0,0 +1,22 @@
+/*
+ * 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.shared.clocks.shared.model
+
+object ClockPreviewConstants {
+    const val KEY_HIDE_CLOCK = "hide_clock"
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/MotionEventsHandlerBase.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/MotionEventsHandlerBase.java
deleted file mode 100644
index 2a64185..0000000
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/MotionEventsHandlerBase.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.plugins;
-
-import android.view.MotionEvent;
-
-/** Handles both trackpad and touch events and report displacements in both axis's. */
-public interface MotionEventsHandlerBase {
-
-    void onMotionEvent(MotionEvent ev);
-
-    float getDisplacementX(MotionEvent ev);
-
-    float getDisplacementY(MotionEvent ev);
-
-    String dump();
-}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/NavigationEdgeBackPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/NavigationEdgeBackPlugin.java
index 054e300..5f6f11c 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/NavigationEdgeBackPlugin.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/NavigationEdgeBackPlugin.java
@@ -48,9 +48,6 @@
     /** Sets the base LayoutParams for the UI. */
     void setLayoutParams(WindowManager.LayoutParams layoutParams);
 
-    /** Sets the motion events handler for the plugin. */
-    default void setMotionEventsHandler(MotionEventsHandlerBase motionEventsHandler) {}
-
     /** Updates the UI based on the motion events passed in device coordinates. */
     void onMotionEvent(MotionEvent motionEvent);
 
diff --git a/packages/SystemUI/res/drawable/ic_sound_bars_anim.xml b/packages/SystemUI/res/drawable/ic_sound_bars_anim.xml
new file mode 100644
index 0000000..43fdec5
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_sound_bars_anim.xml
@@ -0,0 +1,225 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2023 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<animated-vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt">
+    <aapt:attr name="android:drawable">
+        <vector android:height="30dp" android:width="36dp" android:viewportHeight="30"
+                android:viewportWidth="36">
+            <group android:name="_R_G">
+                <group android:name="_R_G_L_2_G" android:translateX="5.939"
+                       android:translateY="26.583" android:pivotY="0.379" android:scaleX="31.96611"
+                       android:scaleY="31.96611">
+                    <path android:name="_R_G_L_2_G_D_0_P_0" android:fillColor="#ffffff"
+                          android:fillAlpha="1" android:fillType="nonZero"
+                          android:pathData=" M0.09 0.27 C0.09,0.27 0.09,0.28 0.09,0.28 C0.09,0.34 0.05,0.38 0,0.38 C-0.05,0.38 -0.09,0.34 -0.09,0.28 C-0.09,0.28 -0.09,0.27 -0.09,0.27 C-0.09,0.22 -0.05,0.18 0,0.18 C0.05,0.18 0.09,0.22 0.09,0.27c "/>
+                </group>
+                <group android:name="_R_G_L_1_G_N_3_T_0" android:translateX="5.939"
+                       android:translateY="26.583" android:pivotY="0.379" android:scaleX="31.96611"
+                       android:scaleY="31.96611">
+                    <group android:name="_R_G_L_1_G" android:translateX="0.379"
+                           android:translateY="0.19">
+                        <path android:name="_R_G_L_1_G_D_0_P_0" android:fillColor="#ffffff"
+                              android:fillAlpha="1" android:fillType="nonZero"
+                              android:pathData=" M0.09 0.09 C0.09,0.09 0.09,0.09 0.09,0.09 C0.09,0.15 0.05,0.19 0,0.19 C-0.05,0.19 -0.09,0.15 -0.09,0.09 C-0.09,0.09 -0.1,0.09 -0.1,0.09 C-0.1,0.04 -0.05,-0.01 0,-0.01 C0.05,-0.01 0.09,0.04 0.09,0.09c "/>
+                    </group>
+                </group>
+                <group android:name="_R_G_L_0_G_N_3_T_0" android:translateX="5.939"
+                       android:translateY="26.583" android:pivotY="0.379" android:scaleX="31.96611"
+                       android:scaleY="31.96611">
+                    <group android:name="_R_G_L_0_G" android:translateX="0.758">
+                        <path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#ffffff"
+                              android:fillAlpha="1" android:fillType="nonZero"
+                              android:pathData=" M0.09 0.28 C0.09,0.28 0.09,0.28 0.09,0.28 C0.09,0.34 0.05,0.38 0,0.38 C-0.05,0.38 -0.09,0.34 -0.09,0.28 C-0.09,0.28 -0.09,0.28 -0.09,0.28 C-0.09,0.22 -0.05,0.18 0,0.18 C0.05,0.18 0.09,0.22 0.09,0.28c "/>
+                    </group>
+                </group>
+            </group>
+            <group android:name="time_group"/>
+        </vector>
+    </aapt:attr>
+    <target android:name="_R_G_L_2_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                        android:repeatCount="infinite"
+                        android:repeatMode="reverse"
+                        android:propertyName="pathData" android:duration="333"
+                        android:startOffset="0"
+                        android:valueFrom="M0.09 -0.28 C0.09,-0.28 0.09,0.28 0.09,0.28 C0.09,0.34 0.05,0.38 0,0.38 C-0.05,0.38 -0.09,0.34 -0.09,0.28 C-0.09,0.28 -0.09,-0.28 -0.09,-0.28 C-0.09,-0.34 -0.05,-0.38 0,-0.38 C0.05,-0.38 0.09,-0.34 0.09,-0.28c "
+                        android:valueTo="M0.09 0.27 C0.09,0.27 0.09,0.28 0.09,0.28 C0.09,0.34 0.05,0.38 0,0.38 C-0.05,0.38 -0.09,0.34 -0.09,0.28 C-0.09,0.28 -0.09,0.27 -0.09,0.27 C-0.09,0.22 -0.05,0.18 0,0.18 C0.05,0.18 0.09,0.22 0.09,0.27c "
+                        android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.595,0 0.372,1 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                        android:repeatCount="infinite"
+                        android:repeatMode="reverse"
+                        android:propertyName="pathData" android:duration="333"
+                        android:startOffset="333"
+                        android:valueFrom="M0.09 0.27 C0.09,0.27 0.09,0.28 0.09,0.28 C0.09,0.34 0.05,0.38 0,0.38 C-0.05,0.38 -0.09,0.34 -0.09,0.28 C-0.09,0.28 -0.09,0.27 -0.09,0.27 C-0.09,0.22 -0.05,0.18 0,0.18 C0.05,0.18 0.09,0.22 0.09,0.27c "
+                        android:valueTo="M0.09 -0.28 C0.09,-0.28 0.09,0.28 0.09,0.28 C0.09,0.34 0.05,0.38 0,0.38 C-0.05,0.38 -0.09,0.34 -0.09,0.28 C-0.09,0.28 -0.09,-0.28 -0.09,-0.28 C-0.09,-0.34 -0.05,-0.38 0,-0.38 C0.05,-0.38 0.09,-0.34 0.09,-0.28c "
+                        android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.595,0 0.372,1 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                        android:repeatCount="infinite"
+                        android:repeatMode="reverse"
+                        android:propertyName="pathData" android:duration="333"
+                        android:startOffset="667"
+                        android:valueFrom="M0.09 -0.28 C0.09,-0.28 0.09,0.28 0.09,0.28 C0.09,0.34 0.05,0.38 0,0.38 C-0.05,0.38 -0.09,0.34 -0.09,0.28 C-0.09,0.28 -0.09,-0.28 -0.09,-0.28 C-0.09,-0.34 -0.05,-0.38 0,-0.38 C0.05,-0.38 0.09,-0.34 0.09,-0.28c "
+                        android:valueTo="M0.09 0.27 C0.09,0.27 0.09,0.28 0.09,0.28 C0.09,0.34 0.05,0.38 0,0.38 C-0.05,0.38 -0.09,0.34 -0.09,0.28 C-0.09,0.28 -0.09,0.27 -0.09,0.27 C-0.09,0.22 -0.05,0.18 0,0.18 C0.05,0.18 0.09,0.22 0.09,0.27c "
+                        android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.595,0 0.372,1 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_1_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                        android:repeatCount="infinite"
+                        android:repeatMode="reverse"
+                        android:propertyName="pathData" android:duration="83"
+                        android:startOffset="0"
+                        android:valueFrom="M0.09 -0.44 C0.09,-0.44 0.09,0.09 0.09,0.09 C0.09,0.15 0.05,0.19 0,0.19 C-0.05,0.19 -0.09,0.15 -0.09,0.09 C-0.09,0.09 -0.1,-0.44 -0.1,-0.44 C-0.1,-0.5 -0.05,-0.54 0,-0.54 C0.05,-0.54 0.09,-0.5 0.09,-0.44c "
+                        android:valueTo="M0.09 -0.49 C0.09,-0.49 0.09,0.09 0.09,0.09 C0.09,0.15 0.05,0.19 0,0.19 C-0.05,0.19 -0.09,0.15 -0.09,0.09 C-0.09,0.09 -0.1,-0.49 -0.1,-0.49 C-0.1,-0.54 -0.05,-0.58 0,-0.58 C0.05,-0.58 0.09,-0.54 0.09,-0.49c "
+                        android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator
+                            android:pathData="M 0.0,0.0 c0.167,0.167 0.372,1 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                        android:repeatCount="infinite"
+                        android:repeatMode="reverse"
+                        android:propertyName="pathData" android:duration="333"
+                        android:startOffset="83"
+                        android:valueFrom="M0.09 -0.49 C0.09,-0.49 0.09,0.09 0.09,0.09 C0.09,0.15 0.05,0.19 0,0.19 C-0.05,0.19 -0.09,0.15 -0.09,0.09 C-0.09,0.09 -0.1,-0.49 -0.1,-0.49 C-0.1,-0.54 -0.05,-0.58 0,-0.58 C0.05,-0.58 0.09,-0.54 0.09,-0.49c "
+                        android:valueTo="M0.09 0.09 C0.09,0.09 0.09,0.09 0.09,0.09 C0.09,0.15 0.05,0.19 0,0.19 C-0.05,0.19 -0.09,0.15 -0.09,0.09 C-0.09,0.09 -0.1,0.09 -0.1,0.09 C-0.1,0.04 -0.05,-0.01 0,-0.01 C0.05,-0.01 0.09,0.04 0.09,0.09c "
+                        android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.595,0 0.372,1 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                        android:repeatCount="infinite"
+                        android:repeatMode="reverse"
+                        android:propertyName="pathData" android:duration="250"
+                        android:startOffset="417"
+                        android:valueFrom="M0.09 0.09 C0.09,0.09 0.09,0.09 0.09,0.09 C0.09,0.15 0.05,0.19 0,0.19 C-0.05,0.19 -0.09,0.15 -0.09,0.09 C-0.09,0.09 -0.1,0.09 -0.1,0.09 C-0.1,0.04 -0.05,-0.01 0,-0.01 C0.05,-0.01 0.09,0.04 0.09,0.09c "
+                        android:valueTo="M0.09 -0.44 C0.09,-0.44 0.09,0.09 0.09,0.09 C0.09,0.15 0.05,0.19 0,0.19 C-0.05,0.19 -0.09,0.15 -0.09,0.09 C-0.09,0.09 -0.1,-0.44 -0.1,-0.44 C-0.1,-0.5 -0.05,-0.54 0,-0.54 C0.05,-0.54 0.09,-0.5 0.09,-0.44c "
+                        android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator
+                            android:pathData="M 0.0,0.0 c0.595,0 0.833,0.833 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                        android:repeatCount="infinite"
+                        android:repeatMode="reverse"
+                        android:propertyName="pathData" android:duration="83"
+                        android:startOffset="667"
+                        android:valueFrom="M0.09 -0.44 C0.09,-0.44 0.09,0.09 0.09,0.09 C0.09,0.15 0.05,0.19 0,0.19 C-0.05,0.19 -0.09,0.15 -0.09,0.09 C-0.09,0.09 -0.1,-0.44 -0.1,-0.44 C-0.1,-0.5 -0.05,-0.54 0,-0.54 C0.05,-0.54 0.09,-0.5 0.09,-0.44c "
+                        android:valueTo="M0.09 -0.49 C0.09,-0.49 0.09,0.09 0.09,0.09 C0.09,0.15 0.05,0.19 0,0.19 C-0.05,0.19 -0.09,0.15 -0.09,0.09 C-0.09,0.09 -0.1,-0.49 -0.1,-0.49 C-0.1,-0.54 -0.05,-0.58 0,-0.58 C0.05,-0.58 0.09,-0.54 0.09,-0.49c "
+                        android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator
+                            android:pathData="M 0.0,0.0 c0.167,0.167 0.372,1 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                        android:repeatCount="infinite"
+                        android:repeatMode="reverse"
+                        android:propertyName="pathData" android:duration="167"
+                        android:startOffset="0"
+                        android:valueFrom="M0.09 -0.02 C0.09,-0.02 0.09,0.28 0.09,0.28 C0.09,0.34 0.05,0.38 0,0.38 C-0.05,0.38 -0.09,0.34 -0.09,0.28 C-0.09,0.28 -0.09,-0.02 -0.09,-0.02 C-0.09,-0.07 -0.05,-0.12 0,-0.12 C0.05,-0.12 0.09,-0.07 0.09,-0.02c "
+                        android:valueTo="M0.09 -0.28 C0.09,-0.28 0.09,0.28 0.09,0.28 C0.09,0.34 0.05,0.38 0,0.38 C-0.05,0.38 -0.09,0.34 -0.09,0.28 C-0.09,0.28 -0.09,-0.28 -0.09,-0.28 C-0.09,-0.34 -0.05,-0.38 0,-0.38 C0.05,-0.38 0.09,-0.34 0.09,-0.28c "
+                        android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator
+                            android:pathData="M 0.0,0.0 c0.167,0.167 0.372,1 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                        android:repeatCount="infinite"
+                        android:repeatMode="reverse"
+                        android:propertyName="pathData" android:duration="333"
+                        android:startOffset="167"
+                        android:valueFrom="M0.09 -0.28 C0.09,-0.28 0.09,0.28 0.09,0.28 C0.09,0.34 0.05,0.38 0,0.38 C-0.05,0.38 -0.09,0.34 -0.09,0.28 C-0.09,0.28 -0.09,-0.28 -0.09,-0.28 C-0.09,-0.34 -0.05,-0.38 0,-0.38 C0.05,-0.38 0.09,-0.34 0.09,-0.28c "
+                        android:valueTo="M0.09 0.28 C0.09,0.28 0.09,0.28 0.09,0.28 C0.09,0.34 0.05,0.38 0,0.38 C-0.05,0.38 -0.09,0.34 -0.09,0.28 C-0.09,0.28 -0.09,0.28 -0.09,0.28 C-0.09,0.22 -0.05,0.18 0,0.18 C0.05,0.18 0.09,0.22 0.09,0.28c "
+                        android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.595,0 0.372,1 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                        android:repeatCount="infinite"
+                        android:repeatMode="reverse"
+                        android:propertyName="pathData" android:duration="167"
+                        android:startOffset="500"
+                        android:valueFrom="M0.09 0.28 C0.09,0.28 0.09,0.28 0.09,0.28 C0.09,0.34 0.05,0.38 0,0.38 C-0.05,0.38 -0.09,0.34 -0.09,0.28 C-0.09,0.28 -0.09,0.28 -0.09,0.28 C-0.09,0.22 -0.05,0.18 0,0.18 C0.05,0.18 0.09,0.22 0.09,0.28c "
+                        android:valueTo="M0.09 -0.02 C0.09,-0.02 0.09,0.28 0.09,0.28 C0.09,0.34 0.05,0.38 0,0.38 C-0.05,0.38 -0.09,0.34 -0.09,0.28 C-0.09,0.28 -0.09,-0.02 -0.09,-0.02 C-0.09,-0.07 -0.05,-0.12 0,-0.12 C0.05,-0.12 0.09,-0.07 0.09,-0.02c "
+                        android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator
+                            android:pathData="M 0.0,0.0 c0.595,0 0.833,0.833 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                        android:repeatCount="infinite"
+                        android:repeatMode="reverse"
+                        android:propertyName="pathData" android:duration="167"
+                        android:startOffset="667"
+                        android:valueFrom="M0.09 -0.02 C0.09,-0.02 0.09,0.28 0.09,0.28 C0.09,0.34 0.05,0.38 0,0.38 C-0.05,0.38 -0.09,0.34 -0.09,0.28 C-0.09,0.28 -0.09,-0.02 -0.09,-0.02 C-0.09,-0.07 -0.05,-0.12 0,-0.12 C0.05,-0.12 0.09,-0.07 0.09,-0.02c "
+                        android:valueTo="M0.09 -0.28 C0.09,-0.28 0.09,0.28 0.09,0.28 C0.09,0.34 0.05,0.38 0,0.38 C-0.05,0.38 -0.09,0.34 -0.09,0.28 C-0.09,0.28 -0.09,-0.28 -0.09,-0.28 C-0.09,-0.34 -0.05,-0.38 0,-0.38 C0.05,-0.38 0.09,-0.34 0.09,-0.28c "
+                        android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator
+                            android:pathData="M 0.0,0.0 c0.167,0.167 0.372,1 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="time_group">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:repeatCount="infinite"
+                    android:repeatMode="reverse"
+                    android:propertyName="translateX"
+                    android:duration="683"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType"/>
+            </set>
+        </aapt:attr>
+    </target>
+</animated-vector>
diff --git a/packages/SystemUI/res/drawable/udfps_enroll_checkmark.xml b/packages/SystemUI/res/drawable/udfps_enroll_checkmark.xml
deleted file mode 100644
index f8169d3..0000000
--- a/packages/SystemUI/res/drawable/udfps_enroll_checkmark.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2021 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="54dp"
-        android:height="54dp"
-        android:viewportWidth="54"
-        android:viewportHeight="54">
-    <path
-        android:pathData="M26.9999,3.9619C39.7029,3.9619 50.0369,14.2969 50.0369,26.9999C50.0369,39.7029 39.7029,50.0379 26.9999,50.0379C14.2969,50.0379 3.9629,39.7029 3.9629,26.9999C3.9629,14.2969 14.2969,3.9619 26.9999,3.9619Z"
-        android:fillColor="?android:colorBackground"
-        android:fillType="evenOdd"/>
-    <path
-        android:pathData="M27,0C12.088,0 0,12.088 0,27C0,41.912 12.088,54 27,54C41.912,54 54,41.912 54,27C54,12.088 41.912,0 27,0ZM27,3.962C39.703,3.962 50.037,14.297 50.037,27C50.037,39.703 39.703,50.038 27,50.038C14.297,50.038 3.963,39.703 3.963,27C3.963,14.297 14.297,3.962 27,3.962Z"
-        android:fillColor="@color/udfps_enroll_progress"
-        android:fillType="evenOdd"/>
-    <path
-        android:pathData="M23.0899,38.8534L10.4199,26.1824L13.2479,23.3544L23.0899,33.1974L41.2389,15.0474L44.0679,17.8754L23.0899,38.8534Z"
-        android:fillColor="@color/udfps_enroll_progress"
-        android:fillType="evenOdd"/>
-</vector>
diff --git a/packages/SystemUI/res/layout/media_ttt_chip_receiver.xml b/packages/SystemUI/res/layout/media_ttt_chip_receiver.xml
index 4483db8..02186fc 100644
--- a/packages/SystemUI/res/layout/media_ttt_chip_receiver.xml
+++ b/packages/SystemUI/res/layout/media_ttt_chip_receiver.xml
@@ -27,22 +27,28 @@
         android:layout_height="wrap_content"
         />
 
-    <com.android.systemui.media.taptotransfer.receiver.ReceiverChipRippleView
-        android:id="@+id/icon_glow_ripple"
+    <FrameLayout
+        android:id="@+id/icon_container_view"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        />
-
-    <!-- Add a bottom margin to avoid the glow of the icon ripple from being cropped by screen
-     bounds while animating with the icon -->
-    <com.android.internal.widget.CachingIconView
-        android:id="@+id/app_icon"
-        android:background="@drawable/media_ttt_chip_background_receiver"
-        android:layout_width="@dimen/media_ttt_icon_size_receiver"
-        android:layout_height="@dimen/media_ttt_icon_size_receiver"
-        android:layout_gravity="center|bottom"
         android:alpha="0.0"
-        android:layout_marginBottom="@dimen/media_ttt_receiver_icon_bottom_margin"
-        />
+        >
+        <com.android.systemui.media.taptotransfer.receiver.ReceiverChipRippleView
+            android:id="@+id/icon_glow_ripple"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            />
+
+        <!-- Add a bottom margin to avoid the glow of the icon ripple from being cropped by screen
+        bounds while animating with the icon -->
+        <com.android.internal.widget.CachingIconView
+            android:id="@+id/app_icon"
+            android:background="@drawable/media_ttt_chip_background_receiver"
+            android:layout_width="@dimen/media_ttt_icon_size_receiver"
+            android:layout_height="@dimen/media_ttt_icon_size_receiver"
+            android:layout_gravity="center|bottom"
+            android:layout_marginBottom="@dimen/media_ttt_receiver_icon_bottom_margin"
+            />
+    </FrameLayout>
 
 </FrameLayout>
diff --git a/packages/SystemUI/res/layout/status_bar_notification_row.xml b/packages/SystemUI/res/layout/status_bar_notification_row.xml
index 2c08f5d..356b36f 100644
--- a/packages/SystemUI/res/layout/status_bar_notification_row.xml
+++ b/packages/SystemUI/res/layout/status_bar_notification_row.xml
@@ -39,8 +39,11 @@
 
     <com.android.systemui.statusbar.notification.row.NotificationContentView
         android:id="@+id/expanded"
-       android:layout_width="match_parent"
-       android:layout_height="wrap_content" />
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:minHeight="@dimen/notification_content_min_height"
+        android:gravity="center_vertical"
+        />
 
     <com.android.systemui.statusbar.notification.row.NotificationContentView
         android:id="@+id/expandedPublic"
diff --git a/packages/SystemUI/res/values-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml
index 08e1bf2..5efcef3 100644
--- a/packages/SystemUI/res/values-night/colors.xml
+++ b/packages/SystemUI/res/values-night/colors.xml
@@ -100,13 +100,4 @@
     <color name="accessibility_floating_menu_message_text">@*android:color/primary_text_default_material_dark</color>
 
     <color name="people_tile_background">@color/material_dynamic_secondary20</color>
-
-    <!-- UDFPS colors -->
-    <color name="udfps_enroll_icon">#7DA7F1</color>
-    <color name="udfps_moving_target_fill">#475670</color>
-    <!-- 50% of udfps_moving_target_fill-->
-    <color name="udfps_moving_target_fill_error">#80475670</color>
-    <color name="udfps_enroll_progress">#7DA7F1</color>
-    <color name="udfps_enroll_progress_help">#607DA7F1</color>
-    <color name="udfps_enroll_progress_help_with_talkback">#FFEE675C</color>
 </resources>
diff --git a/packages/SystemUI/res/values-sw600dp-land/dimens.xml b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
index 5d78e4e..2a27b47 100644
--- a/packages/SystemUI/res/values-sw600dp-land/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
@@ -45,8 +45,6 @@
     <item name="controls_task_view_width_percentage" translatable="false" format="float" type="dimen">0.45</item>
     <dimen name="controls_task_view_right_margin">8dp</dimen>
 
-    <dimen name="status_bar_header_height_keyguard">42dp</dimen>
-
     <dimen name="lockscreen_shade_max_over_scroll_amount">32dp</dimen>
 
     <dimen name="status_view_margin_horizontal">8dp</dimen>
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index 4f24d83..45b137a 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -16,8 +16,9 @@
 */
 -->
 <resources>
-    <!-- Height of the status bar header bar when on Keyguard -->
-    <dimen name="status_bar_header_height_keyguard">60dp</dimen>
+    <!-- Height of the status bar header bar when on Keyguard.
+         On large screens should be the same as the regular status bar. -->
+    <dimen name="status_bar_header_height_keyguard">@dimen/status_bar_height</dimen>
 
     <!-- Size of user icon + frame in the qs user picker (incl. frame) -->
     <dimen name="qs_framed_avatar_size">60dp</dimen>
diff --git a/packages/SystemUI/res/values-sw720dp-land/dimens.xml b/packages/SystemUI/res/values-sw720dp-land/dimens.xml
index 2b88e55..9ed9360 100644
--- a/packages/SystemUI/res/values-sw720dp-land/dimens.xml
+++ b/packages/SystemUI/res/values-sw720dp-land/dimens.xml
@@ -22,8 +22,6 @@
 
     <dimen name="keyguard_split_shade_top_margin">72dp</dimen>
 
-    <dimen name="status_bar_header_height_keyguard">56dp</dimen>
-
     <dimen name="status_view_margin_horizontal">24dp</dimen>
 
     <dimen name="qs_media_session_height_expanded">184dp</dimen>
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index b3256ef..8d8fdf0 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -210,16 +210,6 @@
         <attr name="permissionGrantButtonBottomStyle" format="reference"/>
     </declare-styleable>
 
-    <declare-styleable name="BiometricsEnrollView">
-        <attr name="biometricsEnrollStyle" format="reference" />
-        <attr name="biometricsEnrollIcon" format="reference|color" />
-        <attr name="biometricsMovingTargetFill" format="reference|color" />
-        <attr name="biometricsMovingTargetFillError" format="reference|color" />
-        <attr name="biometricsEnrollProgress" format="reference|color" />
-        <attr name="biometricsEnrollProgressHelp" format="reference|color" />
-        <attr name="biometricsEnrollProgressHelpWithTalkback" format="reference|color" />
-    </declare-styleable>
-
     <declare-styleable name="SeekBarWithIconButtonsView_Layout">
         <attr name="max" format="integer" />
         <attr name="progress" format="integer" />
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 7360c79..e2fdf16 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -136,15 +136,6 @@
     <!-- SFPS colors -->
     <color name="sfps_chevron_fill">@color/material_dynamic_primary90</color>
 
-    <!-- UDFPS colors -->
-    <color name="udfps_enroll_icon">#699FF3</color>
-    <color name="udfps_moving_target_fill">#C2D7F7</color>
-    <!-- 50% of udfps_moving_target_fill-->
-    <color name="udfps_moving_target_fill_error">#80C2D7F7</color>
-    <color name="udfps_enroll_progress">#699FF3</color>
-    <color name="udfps_enroll_progress_help">#70699FF3</color>
-    <color name="udfps_enroll_progress_help_with_talkback">#FFEE675C</color>
-
     <!-- Floating overlay actions -->
     <color name="overlay_button_ripple">#1f000000</color>
     <color name="overlay_background_protection_start">#40000000</color> <!-- 25% black -->
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 050b1e1..d9e9c5db 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -593,11 +593,6 @@
         58.0001 29.2229,56.9551 26.8945,55.195
     </string>
 
-    <!-- The radius of the enrollment progress bar, in dp -->
-    <integer name="config_udfpsEnrollProgressBar" translatable="false">
-        280
-    </integer>
-
     <!-- The time (in ms) needed to trigger the lock icon view's long-press affordance -->
     <integer name="config_lockIconLongPress" translatable="false">200</integer>
 
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 2df2513..36172ca 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -184,6 +184,15 @@
     <!-- Height of a small notification in the status bar-->
     <dimen name="notification_min_height">@*android:dimen/notification_min_height</dimen>
 
+    <!-- Minimum allowed height of notifications -->
+    <dimen name="notification_validation_minimum_allowed_height">10dp</dimen>
+
+    <!-- Minimum height for displaying notification content. -->
+    <dimen name="notification_content_min_height">48dp</dimen>
+
+    <!-- Reference width used when validating notification layouts -->
+    <dimen name="notification_validation_reference_width">320dp</dimen>
+
     <!-- Increased height of a small notification in the status bar -->
     <dimen name="notification_min_height_increased">146dp</dimen>
 
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index bf11019..1720357 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2561,7 +2561,10 @@
     <string name="media_output_broadcast_update_error">Can\u2019t save. Try again.</string>
     <!-- The error message when Broadcast name/code update failed and can't change again[CHAR LIMIT=60] -->
     <string name="media_output_broadcast_last_update_error">Can\u2019t save.</string>
-
+    <!-- The hint message when Broadcast code is less than 4 characters [CHAR LIMIT=60] -->
+    <string name="media_output_broadcast_code_hint_no_less_than_min">Use at least 4 characters</string>
+    <!-- The hint message when Broadcast code is more than 16 characters [CHAR LIMIT=60] -->
+    <string name="media_output_broadcast_code_hint_no_more_than_max">Use fewer than 16 characters</string>
 
     <!-- Label for clip data when copying the build number off QS [CHAR LIMIT=NONE]-->
     <string name="build_number_clip_data_label">Build number</string>
@@ -2843,7 +2846,7 @@
     </string>
 
     <!-- Learn more URL for the log access confirmation dialog. [DO NOT TRANSLATE]-->
-    <string name="log_access_confirmation_learn_more_url" translatable="false">https://support.google.com/android?p=system_logs#topic=7313011</string>
+    <string name="log_access_confirmation_learn_more_url" translatable="false">https://support.google.com/android/answer/12986432</string>
     <string name="log_access_confirmation_learn_more">Learn more</string>
     <string name="log_access_confirmation_learn_more_at">Learn more at <xliff:g id="url" example="g.co/android/devicelogs">%s</xliff:g></string>
 
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 4998d68..9398c89 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -315,10 +315,6 @@
 
         <!-- Needed for MediaRoute chooser dialog -->
         <item name="*android:isLightTheme">false</item>
-
-        <!-- Biometrics enroll color style -->
-        <item name="biometricsEnrollStyle">@style/BiometricsEnrollStyle</item>
-
     </style>
 
     <style name="Theme.SystemUI.LightWallpaper">
@@ -1355,15 +1351,6 @@
         <item name="android:background">@drawable/grant_permissions_buttons_bottom</item>
     </style>
 
-    <style name="BiometricsEnrollStyle">
-        <item name="biometricsEnrollIcon">@color/udfps_enroll_icon</item>
-        <item name="biometricsMovingTargetFill">@color/udfps_moving_target_fill</item>
-        <item name="biometricsMovingTargetFillError">@color/udfps_moving_target_fill_error</item>
-        <item name="biometricsEnrollProgress">@color/udfps_enroll_progress</item>
-        <item name="biometricsEnrollProgressHelp">@color/udfps_enroll_progress_help</item>
-        <item name="biometricsEnrollProgressHelpWithTalkback">@color/udfps_enroll_progress_help_with_talkback</item>
-    </style>
-
     <!-- Magnification styles -->
     <style name="TextAppearance.MagnificationSetting" />
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt
index 1a06b5f..fe8b8c9 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt
@@ -27,6 +27,7 @@
     override var userId: Int = 0,
     override var listening: Boolean = false,
     // keep sorted
+    var alternateBouncerShowing: Boolean = false,
     var authInterruptActive: Boolean = false,
     var biometricSettingEnabledForUser: Boolean = false,
     var bouncerFullyShown: Boolean = false,
@@ -44,7 +45,6 @@
     var secureCameraLaunched: Boolean = false,
     var supportsDetect: Boolean = false,
     var switchingUser: Boolean = false,
-    var udfpsBouncerShowing: Boolean = false,
     var udfpsFingerDown: Boolean = false,
     var userNotTrustedOrDetectionIsNeeded: Boolean = false,
 ) : KeyguardListenModel() {
@@ -73,7 +73,7 @@
             secureCameraLaunched.toString(),
             supportsDetect.toString(),
             switchingUser.toString(),
-            udfpsBouncerShowing.toString(),
+            alternateBouncerShowing.toString(),
             udfpsFingerDown.toString(),
             userNotTrustedOrDetectionIsNeeded.toString(),
         )
@@ -95,6 +95,7 @@
                 userId = model.userId
                 listening = model.listening
                 // keep sorted
+                alternateBouncerShowing = model.alternateBouncerShowing
                 biometricSettingEnabledForUser = model.biometricSettingEnabledForUser
                 bouncerFullyShown = model.bouncerFullyShown
                 faceAndFpNotAuthenticated = model.faceAndFpNotAuthenticated
@@ -111,7 +112,6 @@
                 secureCameraLaunched = model.secureCameraLaunched
                 supportsDetect = model.supportsDetect
                 switchingUser = model.switchingUser
-                udfpsBouncerShowing = model.udfpsBouncerShowing
                 switchingUser = model.switchingUser
                 udfpsFingerDown = model.udfpsFingerDown
                 userNotTrustedOrDetectionIsNeeded = model.userNotTrustedOrDetectionIsNeeded
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index f491232..9f1c382 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -95,6 +95,7 @@
 import android.hardware.biometrics.BiometricManager;
 import android.hardware.biometrics.BiometricSourceType;
 import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
+import android.hardware.biometrics.SensorProperties;
 import android.hardware.face.FaceManager;
 import android.hardware.face.FaceSensorPropertiesInternal;
 import android.hardware.fingerprint.FingerprintManager;
@@ -261,6 +262,7 @@
     @VisibleForTesting
     public static final int BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED = -1;
     public static final int BIOMETRIC_HELP_FACE_NOT_RECOGNIZED = -2;
+    public static final int BIOMETRIC_HELP_FACE_NOT_AVAILABLE = -3;
 
     /**
      * If no cancel signal has been received after this amount of time, set the biometric running
@@ -311,7 +313,7 @@
     private boolean mGoingToSleep;
     private boolean mPrimaryBouncerFullyShown;
     private boolean mPrimaryBouncerIsOrWillBeShowing;
-    private boolean mUdfpsBouncerShowing;
+    private boolean mAlternateBouncerShowing;
     private boolean mAuthInterruptActive;
     private boolean mNeedsSlowUnlockTransition;
     private boolean mAssistantVisible;
@@ -536,7 +538,8 @@
      * It's assumed that the trust was granted for the current user.
      */
     private boolean shouldDismissKeyguardOnTrustGrantedWithCurrentUser(TrustGrantFlags flags) {
-        final boolean isBouncerShowing = mPrimaryBouncerIsOrWillBeShowing || mUdfpsBouncerShowing;
+        final boolean isBouncerShowing =
+                mPrimaryBouncerIsOrWillBeShowing || mAlternateBouncerShowing;
         return (flags.isInitiatedByUser() || flags.dismissKeyguardRequested())
                 && (mDeviceInteractive || flags.temporaryAndRenewable())
                 && (isBouncerShowing || flags.dismissKeyguardRequested());
@@ -1742,7 +1745,7 @@
                 public void onAuthenticationFailed() {
                         String reason =
                                 mKeyguardBypassController.canBypass() ? "bypass"
-                                        : mUdfpsBouncerShowing ? "udfpsBouncer"
+                                        : mAlternateBouncerShowing ? "alternateBouncer"
                                                 : mPrimaryBouncerFullyShown ? "bouncer"
                                                         : "udfpsFpDown";
                         requestActiveUnlock(
@@ -2601,7 +2604,7 @@
         requestActiveUnlock(
                 requestOrigin,
                 extraReason, canFaceBypass
-                        || mUdfpsBouncerShowing
+                        || mAlternateBouncerShowing
                         || mPrimaryBouncerFullyShown
                         || mAuthController.isUdfpsFingerDown());
     }
@@ -2619,23 +2622,23 @@
     }
 
     /**
-     * Whether the UDFPS bouncer is showing.
+     * Whether the alternate bouncer is showing.
      */
-    public void setUdfpsBouncerShowing(boolean showing) {
-        mUdfpsBouncerShowing = showing;
-        if (mUdfpsBouncerShowing) {
+    public void setAlternateBouncerShowing(boolean showing) {
+        mAlternateBouncerShowing = showing;
+        if (mAlternateBouncerShowing) {
             updateFaceListeningState(BIOMETRIC_ACTION_START,
                     FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN);
             requestActiveUnlock(
                     ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT,
-                    "udfpsBouncer");
+                    "alternateBouncer");
         }
     }
 
     private boolean shouldTriggerActiveUnlock() {
         // Triggers:
         final boolean triggerActiveUnlockForAssistant = shouldTriggerActiveUnlockForAssistant();
-        final boolean awakeKeyguard = mPrimaryBouncerFullyShown || mUdfpsBouncerShowing
+        final boolean awakeKeyguard = mPrimaryBouncerFullyShown || mAlternateBouncerShowing
                 || (isKeyguardVisible() && !mGoingToSleep
                 && mStatusBarState != StatusBarState.SHADE_LOCKED);
 
@@ -2819,7 +2822,6 @@
         final boolean isPostureAllowedForFaceAuth =
                 mConfigFaceAuthSupportedPosture == 0 /* DEVICE_POSTURE_UNKNOWN */ ? true
                         : (mPostureState == mConfigFaceAuthSupportedPosture);
-
         // Only listen if this KeyguardUpdateMonitor belongs to the primary user. There is an
         // instance of KeyguardUpdateMonitor for each user but KeyguardUpdateMonitor is user-aware.
         final boolean shouldListen =
@@ -2829,11 +2831,11 @@
                         || awakeKeyguard
                         || shouldListenForFaceAssistant
                         || isUdfpsFingerDown
-                        || mUdfpsBouncerShowing)
+                        || mAlternateBouncerShowing)
                 && !mSwitchingUser && !faceDisabledForUser && userNotTrustedOrDetectionIsNeeded
                 && !mKeyguardGoingAway && biometricEnabledForUser
                 && faceAuthAllowedOrDetectionIsNeeded && mIsPrimaryUser
-                && (!mSecureCameraLaunched || mOccludingAppRequestingFace)
+                && (!mSecureCameraLaunched || mAlternateBouncerShowing)
                 && faceAndFpNotAuthenticated
                 && !mGoingToSleep
                 && isPostureAllowedForFaceAuth;
@@ -2844,6 +2846,7 @@
                     System.currentTimeMillis(),
                     user,
                     shouldListen,
+                    mAlternateBouncerShowing,
                     mAuthInterruptActive,
                     biometricEnabledForUser,
                     mPrimaryBouncerFullyShown,
@@ -2861,7 +2864,6 @@
                     mSecureCameraLaunched,
                     supportsDetect,
                     mSwitchingUser,
-                    mUdfpsBouncerShowing,
                     isUdfpsFingerDown,
                     userNotTrustedOrDetectionIsNeeded));
 
@@ -2949,9 +2951,26 @@
             // This would need to be updated for multi-sensor devices
             final boolean supportsFaceDetection = !mFaceSensorProperties.isEmpty()
                     && mFaceSensorProperties.get(0).supportsFaceDetection;
-            if (!isUnlockingWithBiometricAllowed(FACE) && supportsFaceDetection) {
-                mLogger.v("startListeningForFace - detect");
-                mFaceManager.detectFace(mFaceCancelSignal, mFaceDetectionCallback, userId);
+            if (!isUnlockingWithBiometricAllowed(FACE)) {
+                final boolean udfpsFingerprintAuthRunning = isUdfpsSupported()
+                        && isFingerprintDetectionRunning();
+                if (supportsFaceDetection && !udfpsFingerprintAuthRunning) {
+                    // Run face detection. (If a face is detected, show the bouncer.)
+                    mLogger.v("startListeningForFace - detect");
+                    mFaceManager.detectFace(mFaceCancelSignal, mFaceDetectionCallback, userId);
+                } else {
+                    // Don't run face detection. Instead, inform the user
+                    // face auth is unavailable and how to proceed.
+                    // (ie: "Use fingerprint instead" or "Swipe up to open")
+                    mLogger.v("Ignoring \"startListeningForFace - detect\". "
+                            + "Informing user face isn't available.");
+                    mFaceAuthenticationCallback.onAuthenticationHelp(
+                            BIOMETRIC_HELP_FACE_NOT_AVAILABLE,
+                            mContext.getResources().getString(
+                                    R.string.keyguard_face_unlock_unavailable)
+                    );
+                    return;
+                }
             } else {
                 mLogger.v("startListeningForFace - authenticate");
                 final boolean isBypassEnabled = mKeyguardBypassController != null
@@ -2982,6 +3001,23 @@
         return isUnlockWithFacePossible(userId) || isUnlockWithFingerprintPossible(userId);
     }
 
+    /**
+     * If non-strong (i.e. weak or convenience) biometrics hardware is available, not disabled, and
+     * user has enrolled templates. This does NOT check if the device is encrypted or in lockdown.
+     *
+     * @param userId User that's trying to unlock.
+     * @return {@code true} if possible.
+     */
+    public boolean isUnlockingWithNonStrongBiometricsPossible(int userId) {
+        // This assumes that there is at most one face and at most one fingerprint sensor
+        return (mFaceManager != null && !mFaceSensorProperties.isEmpty()
+                && (mFaceSensorProperties.get(0).sensorStrength != SensorProperties.STRENGTH_STRONG)
+                && isUnlockWithFacePossible(userId))
+                || (mFpm != null && !mFingerprintSensorProperties.isEmpty()
+                && (mFingerprintSensorProperties.get(0).sensorStrength
+                != SensorProperties.STRENGTH_STRONG) && isUnlockWithFingerprintPossible(userId));
+    }
+
     @SuppressLint("MissingPermission")
     @VisibleForTesting
     boolean isUnlockWithFingerprintPossible(int userId) {
@@ -3956,7 +3992,7 @@
                 pw.println("        mPrimaryBouncerIsOrWillBeShowing="
                         + mPrimaryBouncerIsOrWillBeShowing);
                 pw.println("        mStatusBarState=" + StatusBarState.toString(mStatusBarState));
-                pw.println("        mUdfpsBouncerShowing=" + mUdfpsBouncerShowing);
+                pw.println("        mAlternateBouncerShowing=" + mAlternateBouncerShowing);
             } else if (isSfpsSupported()) {
                 pw.println("        sfpsEnrolled=" + isSfpsEnrolled());
                 pw.println("        shouldListenForSfps=" + shouldListenForFingerprint(false));
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
index 2c7eceb..379c78a 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
@@ -16,9 +16,11 @@
 
 package com.android.keyguard.logging
 
+import com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController
 import com.android.systemui.log.dagger.KeyguardLog
 import com.android.systemui.plugins.log.LogBuffer
 import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.statusbar.KeyguardIndicationController
 import com.google.errorprone.annotations.CompileTimeConstant
 import javax.inject.Inject
 
@@ -76,4 +78,46 @@
             { "$str1 msgId: $str2 msg: $str3" }
         )
     }
+
+    fun logUpdateDeviceEntryIndication(
+        animate: Boolean,
+        visible: Boolean,
+        dozing: Boolean,
+    ) {
+        buffer.log(
+            KeyguardIndicationController.TAG,
+            LogLevel.DEBUG,
+            {
+                bool1 = animate
+                bool2 = visible
+                bool3 = dozing
+            },
+            { "updateDeviceEntryIndication animate:$bool1 visible:$bool2 dozing $bool3" }
+        )
+    }
+
+    fun logKeyguardSwitchIndication(
+        type: Int,
+        message: String?,
+    ) {
+        buffer.log(
+            KeyguardIndicationController.TAG,
+            LogLevel.DEBUG,
+            {
+                int1 = type
+                str1 = message
+            },
+            { "keyguardSwitchIndication ${getKeyguardSwitchIndicationNonSensitiveLog(int1, str1)}" }
+        )
+    }
+
+    fun getKeyguardSwitchIndicationNonSensitiveLog(type: Int, message: String?): String {
+        // only show the battery string. other strings may contain sensitive info
+        return if (type == KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BATTERY) {
+            "type=${KeyguardIndicationRotateTextViewController.indicationTypeToString(type)}" +
+                " message=$message"
+        } else {
+            "type=${KeyguardIndicationRotateTextViewController.indicationTypeToString(type)}"
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 074928a..9e83264 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -307,34 +307,15 @@
                         unconfigureDisplay(view);
                     }
                     tryAodSendFingerUp();
-                    if (acquiredGood) {
-                        mOverlay.onAcquiredGood();
-                    }
                 });
             }
         }
 
         @Override
-        public void onEnrollmentProgress(int sensorId, int remaining) {
-            mFgExecutor.execute(() -> {
-                if (mOverlay == null) {
-                    Log.e(TAG, "onEnrollProgress received but serverRequest is null");
-                    return;
-                }
-                mOverlay.onEnrollmentProgress(remaining);
-            });
-        }
+        public void onEnrollmentProgress(int sensorId, int remaining) { }
 
         @Override
-        public void onEnrollmentHelp(int sensorId) {
-            mFgExecutor.execute(() -> {
-                if (mOverlay == null) {
-                    Log.e(TAG, "onEnrollmentHelp received but serverRequest is null");
-                    return;
-                }
-                mOverlay.onEnrollmentHelp();
-            });
-        }
+        public void onEnrollmentHelp(int sensorId) { }
 
         @Override
         public void setDebugMessage(int sensorId, String message) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index 55bacef..414c2ec 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -34,7 +34,6 @@
 import android.os.Build
 import android.os.RemoteException
 import android.provider.Settings
-import android.util.FeatureFlagUtils
 import android.util.Log
 import android.util.RotationUtils
 import android.view.LayoutInflater
@@ -135,14 +134,6 @@
         }
     }
 
-    /** A helper if the [requestReason] was due to enrollment. */
-    val enrollHelper: UdfpsEnrollHelper? =
-        if (requestReason.isEnrollmentReason() && !shouldRemoveEnrollmentUi()) {
-            UdfpsEnrollHelper(context, fingerprintManager, secureSettings, requestReason)
-        } else {
-            null
-        }
-
     /** If the overlay is currently showing. */
     val isShowing: Boolean
         get() = overlayView != null
@@ -239,32 +230,16 @@
         return when (filteredRequestReason) {
             REASON_ENROLL_FIND_SENSOR,
             REASON_ENROLL_ENROLLING -> {
-                if (FeatureFlagUtils.isEnabled(context,
-                                FeatureFlagUtils.SETTINGS_SHOW_UDFPS_ENROLL_IN_SETTINGS)) {
-                    // Enroll udfps UI is handled by settings, so use empty view here
-                    UdfpsFpmEmptyViewController(
-                            view.addUdfpsView(R.layout.udfps_fpm_empty_view){
-                                updateAccessibilityViewLocation(sensorBounds)
-                            },
-                            statusBarStateController,
-                            shadeExpansionStateManager,
-                            dialogManager,
-                            dumpManager
-                    )
-                } else {
-                    UdfpsEnrollViewController(
-                            view.addUdfpsView(R.layout.udfps_enroll_view) {
-                                updateSensorLocation(sensorBounds)
-                            },
-                            enrollHelper ?: throw IllegalStateException("no enrollment helper"),
-                            statusBarStateController,
-                            shadeExpansionStateManager,
-                            dialogManager,
-                            dumpManager,
-                            featureFlags,
-                            overlayParams.scaleFactor
-                    )
-                }
+                // Enroll udfps UI is handled by settings, so use empty view here
+                UdfpsFpmEmptyViewController(
+                    view.addUdfpsView(R.layout.udfps_fpm_empty_view){
+                        updateAccessibilityViewLocation(sensorBounds)
+                    },
+                    statusBarStateController,
+                    shadeExpansionStateManager,
+                    dialogManager,
+                    dumpManager
+                )
             }
             REASON_AUTH_KEYGUARD -> {
                 UdfpsKeyguardViewController(
@@ -337,18 +312,6 @@
         return wasShowing
     }
 
-    fun onEnrollmentProgress(remaining: Int) {
-        enrollHelper?.onEnrollmentProgress(remaining)
-    }
-
-    fun onAcquiredGood() {
-        enrollHelper?.animateIfLastStep()
-    }
-
-    fun onEnrollmentHelp() {
-        enrollHelper?.onEnrollmentHelp()
-    }
-
     /**
      * This function computes the angle of touch relative to the sensor and maps
      * the angle to a list of help messages which are announced if accessibility is enabled.
@@ -457,10 +420,6 @@
 }
 
 @ShowReason
-private fun Int.isEnrollmentReason() =
-    this == REASON_ENROLL_FIND_SENSOR || this == REASON_ENROLL_ENROLLING
-
-@ShowReason
 private fun Int.isImportantForAccessibility() =
     this == REASON_ENROLL_FIND_SENSOR ||
             this == REASON_ENROLL_ENROLLING ||
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java
deleted file mode 100644
index 3e1c4e5..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java
+++ /dev/null
@@ -1,252 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.biometrics;
-
-import android.animation.Animator;
-import android.animation.AnimatorSet;
-import android.animation.ValueAnimator;
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.PointF;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.drawable.Drawable;
-import android.os.Handler;
-import android.os.Looper;
-import android.util.AttributeSet;
-import android.view.animation.AccelerateDecelerateInterpolator;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.systemui.R;
-
-/**
- * UDFPS fingerprint drawable that is shown when enrolling
- */
-public class UdfpsEnrollDrawable extends UdfpsDrawable {
-    private static final String TAG = "UdfpsAnimationEnroll";
-
-    private static final long TARGET_ANIM_DURATION_LONG = 800L;
-    private static final long TARGET_ANIM_DURATION_SHORT = 600L;
-    // 1 + SCALE_MAX is the maximum that the moving target will animate to
-    private static final float SCALE_MAX = 0.25f;
-
-    private final Handler mHandler = new Handler(Looper.getMainLooper());
-
-    @NonNull private final Drawable mMovingTargetFpIcon;
-    @NonNull private final Paint mSensorOutlinePaint;
-    @NonNull private final Paint mBlueFill;
-
-    @Nullable private RectF mSensorRect;
-    @Nullable private UdfpsEnrollHelper mEnrollHelper;
-
-    // Moving target animator set
-    @Nullable AnimatorSet mTargetAnimatorSet;
-    // Moving target location
-    float mCurrentX;
-    float mCurrentY;
-    // Moving target size
-    float mCurrentScale = 1.f;
-
-    @NonNull private final Animator.AnimatorListener mTargetAnimListener;
-
-    private boolean mShouldShowTipHint = false;
-    private boolean mShouldShowEdgeHint = false;
-
-    private int mEnrollIcon;
-    private int mMovingTargetFill;
-
-    UdfpsEnrollDrawable(@NonNull Context context, @Nullable AttributeSet attrs) {
-        super(context);
-
-        loadResources(context, attrs);
-        mSensorOutlinePaint = new Paint(0 /* flags */);
-        mSensorOutlinePaint.setAntiAlias(true);
-        mSensorOutlinePaint.setColor(mMovingTargetFill);
-        mSensorOutlinePaint.setStyle(Paint.Style.FILL);
-
-        mBlueFill = new Paint(0 /* flags */);
-        mBlueFill.setAntiAlias(true);
-        mBlueFill.setColor(mMovingTargetFill);
-        mBlueFill.setStyle(Paint.Style.FILL);
-
-        mMovingTargetFpIcon = context.getResources()
-                .getDrawable(R.drawable.ic_kg_fingerprint, null);
-        mMovingTargetFpIcon.setTint(mEnrollIcon);
-        mMovingTargetFpIcon.mutate();
-
-        getFingerprintDrawable().setTint(mEnrollIcon);
-
-        mTargetAnimListener = new Animator.AnimatorListener() {
-            @Override
-            public void onAnimationStart(Animator animation) {}
-
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                updateTipHintVisibility();
-            }
-
-            @Override
-            public void onAnimationCancel(Animator animation) {}
-
-            @Override
-            public void onAnimationRepeat(Animator animation) {}
-        };
-    }
-
-    void loadResources(Context context, @Nullable AttributeSet attrs) {
-        final TypedArray ta = context.obtainStyledAttributes(attrs,
-                R.styleable.BiometricsEnrollView, R.attr.biometricsEnrollStyle,
-                R.style.BiometricsEnrollStyle);
-        mEnrollIcon = ta.getColor(R.styleable.BiometricsEnrollView_biometricsEnrollIcon, 0);
-        mMovingTargetFill = ta.getColor(
-                R.styleable.BiometricsEnrollView_biometricsMovingTargetFill, 0);
-        ta.recycle();
-    }
-
-    void setEnrollHelper(@NonNull UdfpsEnrollHelper helper) {
-        mEnrollHelper = helper;
-    }
-
-    @Override
-    public void onSensorRectUpdated(@NonNull RectF sensorRect) {
-        super.onSensorRectUpdated(sensorRect);
-        mSensorRect = sensorRect;
-    }
-
-    @Override
-    protected void updateFingerprintIconBounds(@NonNull Rect bounds) {
-        super.updateFingerprintIconBounds(bounds);
-        mMovingTargetFpIcon.setBounds(bounds);
-        invalidateSelf();
-    }
-
-    void onEnrollmentProgress(int remaining, int totalSteps) {
-        if (mEnrollHelper == null) {
-            return;
-        }
-
-        if (!mEnrollHelper.isCenterEnrollmentStage()) {
-            if (mTargetAnimatorSet != null && mTargetAnimatorSet.isRunning()) {
-                mTargetAnimatorSet.end();
-            }
-
-            final PointF point = mEnrollHelper.getNextGuidedEnrollmentPoint();
-            if (mCurrentX != point.x || mCurrentY != point.y) {
-                final ValueAnimator x = ValueAnimator.ofFloat(mCurrentX, point.x);
-                x.addUpdateListener(animation -> {
-                    mCurrentX = (float) animation.getAnimatedValue();
-                    invalidateSelf();
-                });
-
-                final ValueAnimator y = ValueAnimator.ofFloat(mCurrentY, point.y);
-                y.addUpdateListener(animation -> {
-                    mCurrentY = (float) animation.getAnimatedValue();
-                    invalidateSelf();
-                });
-
-                final boolean isMovingToCenter = point.x == 0f && point.y == 0f;
-                final long duration = isMovingToCenter
-                        ? TARGET_ANIM_DURATION_SHORT
-                        : TARGET_ANIM_DURATION_LONG;
-
-                final ValueAnimator scale = ValueAnimator.ofFloat(0, (float) Math.PI);
-                scale.setDuration(duration);
-                scale.addUpdateListener(animation -> {
-                    // Grow then shrink
-                    mCurrentScale = 1
-                            + SCALE_MAX * (float) Math.sin((float) animation.getAnimatedValue());
-                    invalidateSelf();
-                });
-
-                mTargetAnimatorSet = new AnimatorSet();
-
-                mTargetAnimatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
-                mTargetAnimatorSet.setDuration(duration);
-                mTargetAnimatorSet.addListener(mTargetAnimListener);
-                mTargetAnimatorSet.playTogether(x, y, scale);
-                mTargetAnimatorSet.start();
-            } else {
-                updateTipHintVisibility();
-            }
-        } else {
-            updateTipHintVisibility();
-        }
-
-        updateEdgeHintVisibility();
-    }
-
-    private void updateTipHintVisibility() {
-        final boolean shouldShow = mEnrollHelper != null && mEnrollHelper.isTipEnrollmentStage();
-        // With the new update, we will git rid of most of this code, and instead
-        // we will change the fingerprint icon.
-        if (mShouldShowTipHint == shouldShow) {
-            return;
-        }
-        mShouldShowTipHint = shouldShow;
-    }
-
-    private void updateEdgeHintVisibility() {
-        final boolean shouldShow = mEnrollHelper != null && mEnrollHelper.isEdgeEnrollmentStage();
-        if (mShouldShowEdgeHint == shouldShow) {
-            return;
-        }
-        mShouldShowEdgeHint = shouldShow;
-    }
-
-    @Override
-    public void draw(@NonNull Canvas canvas) {
-        if (isDisplayConfigured()) {
-            return;
-        }
-
-        // Draw moving target
-        if (mEnrollHelper != null && !mEnrollHelper.isCenterEnrollmentStage()) {
-            canvas.save();
-            canvas.translate(mCurrentX, mCurrentY);
-
-            if (mSensorRect != null) {
-                canvas.scale(mCurrentScale, mCurrentScale,
-                        mSensorRect.centerX(), mSensorRect.centerY());
-                canvas.drawOval(mSensorRect, mBlueFill);
-            }
-
-            mMovingTargetFpIcon.draw(canvas);
-            canvas.restore();
-        } else {
-            if (mSensorRect != null) {
-                canvas.drawOval(mSensorRect, mSensorOutlinePaint);
-            }
-            getFingerprintDrawable().draw(canvas);
-            getFingerprintDrawable().setAlpha(getAlpha());
-            mSensorOutlinePaint.setAlpha(getAlpha());
-        }
-
-    }
-
-    @Override
-    public void setAlpha(int alpha) {
-        super.setAlpha(alpha);
-        mSensorOutlinePaint.setAlpha(alpha);
-        mBlueFill.setAlpha(alpha);
-        mMovingTargetFpIcon.setAlpha(alpha);
-        invalidateSelf();
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java
deleted file mode 100644
index cfa8ec5..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java
+++ /dev/null
@@ -1,233 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.biometrics;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.graphics.PointF;
-import android.hardware.biometrics.BiometricOverlayConstants;
-import android.hardware.fingerprint.FingerprintManager;
-import android.os.Build;
-import android.os.UserHandle;
-import android.util.Log;
-import android.util.TypedValue;
-import android.view.accessibility.AccessibilityManager;
-
-import com.android.systemui.util.settings.SecureSettings;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Helps keep track of enrollment state and animates the progress bar accordingly.
- */
-public class UdfpsEnrollHelper {
-    private static final String TAG = "UdfpsEnrollHelper";
-
-    private static final String SCALE_OVERRIDE =
-            "com.android.systemui.biometrics.UdfpsEnrollHelper.scale";
-    private static final float SCALE = 0.5f;
-
-    private static final String NEW_COORDS_OVERRIDE =
-            "com.android.systemui.biometrics.UdfpsNewCoords";
-
-    interface Listener {
-        void onEnrollmentProgress(int remaining, int totalSteps);
-        void onEnrollmentHelp(int remaining, int totalSteps);
-        void onLastStepAcquired();
-    }
-
-    @NonNull private final FingerprintManager mFingerprintManager;
-    @NonNull private final SecureSettings mSecureSettings;
-    // IUdfpsOverlayController reason
-    private final int mEnrollReason;
-    private final boolean mAccessibilityEnabled;
-    @NonNull private final List<PointF> mGuidedEnrollmentPoints;
-
-    private int mTotalSteps = -1;
-    private int mRemainingSteps = -1;
-
-    // Note that this is actually not equal to "mTotalSteps - mRemainingSteps", because the
-    // interface makes no promises about monotonically increasing by one each time.
-    private int mLocationsEnrolled = 0;
-
-    private int mCenterTouchCount = 0;
-
-    @Nullable Listener mListener;
-
-    public UdfpsEnrollHelper(@NonNull Context context,
-            @NonNull FingerprintManager fingerprintManager, SecureSettings secureSettings,
-            int reason) {
-
-        mFingerprintManager = fingerprintManager;
-        mSecureSettings = secureSettings;
-        mEnrollReason = reason;
-
-        final AccessibilityManager am = context.getSystemService(AccessibilityManager.class);
-        mAccessibilityEnabled = am.isEnabled();
-
-        mGuidedEnrollmentPoints = new ArrayList<>();
-
-        // Number of pixels per mm
-        float px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_MM, 1,
-                context.getResources().getDisplayMetrics());
-        boolean useNewCoords = mSecureSettings.getIntForUser(NEW_COORDS_OVERRIDE, 0,
-                UserHandle.USER_CURRENT) != 0;
-        if (useNewCoords && (Build.IS_ENG || Build.IS_USERDEBUG)) {
-            Log.v(TAG, "Using new coordinates");
-            mGuidedEnrollmentPoints.add(new PointF(-0.15f * px, -1.02f * px));
-            mGuidedEnrollmentPoints.add(new PointF(-0.15f * px,  1.02f * px));
-            mGuidedEnrollmentPoints.add(new PointF( 0.29f * px,  0.00f * px));
-            mGuidedEnrollmentPoints.add(new PointF( 2.17f * px, -2.35f * px));
-            mGuidedEnrollmentPoints.add(new PointF( 1.07f * px, -3.96f * px));
-            mGuidedEnrollmentPoints.add(new PointF(-0.37f * px, -4.31f * px));
-            mGuidedEnrollmentPoints.add(new PointF(-1.69f * px, -3.29f * px));
-            mGuidedEnrollmentPoints.add(new PointF(-2.48f * px, -1.23f * px));
-            mGuidedEnrollmentPoints.add(new PointF(-2.48f * px,  1.23f * px));
-            mGuidedEnrollmentPoints.add(new PointF(-1.69f * px,  3.29f * px));
-            mGuidedEnrollmentPoints.add(new PointF(-0.37f * px,  4.31f * px));
-            mGuidedEnrollmentPoints.add(new PointF( 1.07f * px,  3.96f * px));
-            mGuidedEnrollmentPoints.add(new PointF( 2.17f * px,  2.35f * px));
-            mGuidedEnrollmentPoints.add(new PointF( 2.58f * px,  0.00f * px));
-        } else {
-            Log.v(TAG, "Using old coordinates");
-            mGuidedEnrollmentPoints.add(new PointF( 2.00f * px,  0.00f * px));
-            mGuidedEnrollmentPoints.add(new PointF( 0.87f * px, -2.70f * px));
-            mGuidedEnrollmentPoints.add(new PointF(-1.80f * px, -1.31f * px));
-            mGuidedEnrollmentPoints.add(new PointF(-1.80f * px,  1.31f * px));
-            mGuidedEnrollmentPoints.add(new PointF( 0.88f * px,  2.70f * px));
-            mGuidedEnrollmentPoints.add(new PointF( 3.94f * px, -1.06f * px));
-            mGuidedEnrollmentPoints.add(new PointF( 2.90f * px, -4.14f * px));
-            mGuidedEnrollmentPoints.add(new PointF(-0.52f * px, -5.95f * px));
-            mGuidedEnrollmentPoints.add(new PointF(-3.33f * px, -3.33f * px));
-            mGuidedEnrollmentPoints.add(new PointF(-3.99f * px, -0.35f * px));
-            mGuidedEnrollmentPoints.add(new PointF(-3.62f * px,  2.54f * px));
-            mGuidedEnrollmentPoints.add(new PointF(-1.49f * px,  5.57f * px));
-            mGuidedEnrollmentPoints.add(new PointF( 2.29f * px,  4.92f * px));
-            mGuidedEnrollmentPoints.add(new PointF( 3.82f * px,  1.78f * px));
-        }
-    }
-
-    int getStageCount() {
-        return mFingerprintManager.getEnrollStageCount();
-    }
-
-    int getStageThresholdSteps(int totalSteps, int stageIndex) {
-        return Math.round(totalSteps * mFingerprintManager.getEnrollStageThreshold(stageIndex));
-    }
-
-    boolean shouldShowProgressBar() {
-        return mEnrollReason == BiometricOverlayConstants.REASON_ENROLL_ENROLLING;
-    }
-
-    void onEnrollmentProgress(int remaining) {
-        if (mTotalSteps == -1) {
-            mTotalSteps = remaining;
-        }
-
-        if (remaining != mRemainingSteps) {
-            mLocationsEnrolled++;
-            if (isCenterEnrollmentStage()) {
-                mCenterTouchCount++;
-            }
-        }
-
-        mRemainingSteps = remaining;
-
-        if (mListener != null) {
-            mListener.onEnrollmentProgress(remaining, mTotalSteps);
-        }
-    }
-
-    void onEnrollmentHelp() {
-        if (mListener != null) {
-            mListener.onEnrollmentHelp(mRemainingSteps, mTotalSteps);
-        }
-    }
-
-    void setListener(Listener listener) {
-        mListener = listener;
-
-        // Only notify during setListener if enrollment is already in progress, so the progress
-        // bar can be updated. If enrollment has not started yet, the progress bar will be empty
-        // anyway.
-        if (mListener != null && mTotalSteps != -1) {
-            mListener.onEnrollmentProgress(mRemainingSteps, mTotalSteps);
-        }
-    }
-
-    boolean isCenterEnrollmentStage() {
-        if (mTotalSteps == -1 || mRemainingSteps == -1) {
-            return true;
-        }
-        return mTotalSteps - mRemainingSteps < getStageThresholdSteps(mTotalSteps, 0);
-    }
-
-    boolean isGuidedEnrollmentStage() {
-        if (mAccessibilityEnabled || mTotalSteps == -1 || mRemainingSteps == -1) {
-            return false;
-        }
-        final int progressSteps = mTotalSteps - mRemainingSteps;
-        return progressSteps >= getStageThresholdSteps(mTotalSteps, 0)
-                && progressSteps < getStageThresholdSteps(mTotalSteps, 1);
-    }
-
-    boolean isTipEnrollmentStage() {
-        if (mTotalSteps == -1 || mRemainingSteps == -1) {
-            return false;
-        }
-        final int progressSteps = mTotalSteps - mRemainingSteps;
-        return progressSteps >= getStageThresholdSteps(mTotalSteps, 1)
-                && progressSteps < getStageThresholdSteps(mTotalSteps, 2);
-    }
-
-    boolean isEdgeEnrollmentStage() {
-        if (mTotalSteps == -1 || mRemainingSteps == -1) {
-            return false;
-        }
-        return mTotalSteps - mRemainingSteps >= getStageThresholdSteps(mTotalSteps, 2);
-    }
-
-    @NonNull
-    PointF getNextGuidedEnrollmentPoint() {
-        if (mAccessibilityEnabled || !isGuidedEnrollmentStage()) {
-            return new PointF(0f, 0f);
-        }
-
-        float scale = SCALE;
-        if (Build.IS_ENG || Build.IS_USERDEBUG) {
-            scale = mSecureSettings.getFloatForUser(SCALE_OVERRIDE, SCALE,
-                    UserHandle.USER_CURRENT);
-        }
-        final int index = mLocationsEnrolled - mCenterTouchCount;
-        final PointF originalPoint = mGuidedEnrollmentPoints
-                .get(index % mGuidedEnrollmentPoints.size());
-        return new PointF(originalPoint.x * scale, originalPoint.y * scale);
-    }
-
-    void animateIfLastStep() {
-        if (mListener == null) {
-            Log.e(TAG, "animateIfLastStep, null listener");
-            return;
-        }
-
-        if (mRemainingSteps <= 2 && mRemainingSteps >= 0) {
-            mListener.onLastStepAcquired();
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
deleted file mode 100644
index 66a8424..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
+++ /dev/null
@@ -1,400 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.biometrics;
-
-import android.animation.ValueAnimator;
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.ColorFilter;
-import android.graphics.Paint;
-import android.graphics.drawable.Drawable;
-import android.os.Process;
-import android.os.VibrationAttributes;
-import android.os.VibrationEffect;
-import android.os.Vibrator;
-import android.util.AttributeSet;
-import android.view.accessibility.AccessibilityManager;
-import android.view.animation.DecelerateInterpolator;
-import android.view.animation.Interpolator;
-import android.view.animation.OvershootInterpolator;
-
-import androidx.annotation.ColorInt;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.systemui.R;
-
-/**
- * UDFPS enrollment progress bar.
- */
-public class UdfpsEnrollProgressBarDrawable extends Drawable {
-    private static final String TAG = "UdfpsProgressBar";
-
-    private static final long CHECKMARK_ANIMATION_DELAY_MS = 200L;
-    private static final long CHECKMARK_ANIMATION_DURATION_MS = 300L;
-    private static final long FILL_COLOR_ANIMATION_DURATION_MS = 350L;
-    private static final long PROGRESS_ANIMATION_DURATION_MS = 400L;
-    private static final float STROKE_WIDTH_DP = 12f;
-    private static final Interpolator DEACCEL = new DecelerateInterpolator();
-
-    private static final VibrationEffect VIBRATE_EFFECT_ERROR =
-            VibrationEffect.createWaveform(new long[] {0, 5, 55, 60}, -1);
-    private static final VibrationAttributes FINGERPRINT_ENROLLING_SONFICATION_ATTRIBUTES =
-            VibrationAttributes.createForUsage(VibrationAttributes.USAGE_ACCESSIBILITY);
-
-    private static final VibrationAttributes HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES =
-            VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK);
-
-    private static final VibrationEffect SUCCESS_VIBRATION_EFFECT =
-            VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
-
-    private final float mStrokeWidthPx;
-    @ColorInt private final int mProgressColor;
-    @ColorInt private final int mHelpColor;
-    @ColorInt private final int mOnFirstBucketFailedColor;
-    @NonNull private final Drawable mCheckmarkDrawable;
-    @NonNull private final Interpolator mCheckmarkInterpolator;
-    @NonNull private final Paint mBackgroundPaint;
-    @NonNull private final Paint mFillPaint;
-    @NonNull private final Vibrator mVibrator;
-    @NonNull private final boolean mIsAccessibilityEnabled;
-    @NonNull private final Context mContext;
-
-    private boolean mAfterFirstTouch;
-
-    private int mRemainingSteps = 0;
-    private int mTotalSteps = 0;
-    private float mProgress = 0f;
-    @Nullable private ValueAnimator mProgressAnimator;
-    @NonNull private final ValueAnimator.AnimatorUpdateListener mProgressUpdateListener;
-
-    private boolean mShowingHelp = false;
-    @Nullable private ValueAnimator mFillColorAnimator;
-    @NonNull private final ValueAnimator.AnimatorUpdateListener mFillColorUpdateListener;
-
-    @Nullable private ValueAnimator mBackgroundColorAnimator;
-    @NonNull private final ValueAnimator.AnimatorUpdateListener mBackgroundColorUpdateListener;
-
-    private boolean mComplete = false;
-    private float mCheckmarkScale = 0f;
-    @Nullable private ValueAnimator mCheckmarkAnimator;
-    @NonNull private final ValueAnimator.AnimatorUpdateListener mCheckmarkUpdateListener;
-
-    private int mMovingTargetFill;
-    private int mMovingTargetFillError;
-    private int mEnrollProgress;
-    private int mEnrollProgressHelp;
-    private int mEnrollProgressHelpWithTalkback;
-
-    public UdfpsEnrollProgressBarDrawable(@NonNull Context context, @Nullable AttributeSet attrs) {
-        mContext = context;
-
-        loadResources(context, attrs);
-        mStrokeWidthPx = Utils.dpToPixels(context, STROKE_WIDTH_DP);
-        mProgressColor = mEnrollProgress;
-        final AccessibilityManager am = context.getSystemService(AccessibilityManager.class);
-        mIsAccessibilityEnabled = am.isTouchExplorationEnabled();
-        mOnFirstBucketFailedColor = mMovingTargetFillError;
-        if (!mIsAccessibilityEnabled) {
-            mHelpColor = mEnrollProgressHelp;
-        } else {
-            mHelpColor = mEnrollProgressHelpWithTalkback;
-        }
-        mCheckmarkDrawable = context.getDrawable(R.drawable.udfps_enroll_checkmark);
-        mCheckmarkDrawable.mutate();
-        mCheckmarkInterpolator = new OvershootInterpolator();
-
-        mBackgroundPaint = new Paint();
-        mBackgroundPaint.setStrokeWidth(mStrokeWidthPx);
-        mBackgroundPaint.setColor(mMovingTargetFill);
-        mBackgroundPaint.setAntiAlias(true);
-        mBackgroundPaint.setStyle(Paint.Style.STROKE);
-        mBackgroundPaint.setStrokeCap(Paint.Cap.ROUND);
-
-        // Progress fill should *not* use the extracted system color.
-        mFillPaint = new Paint();
-        mFillPaint.setStrokeWidth(mStrokeWidthPx);
-        mFillPaint.setColor(mProgressColor);
-        mFillPaint.setAntiAlias(true);
-        mFillPaint.setStyle(Paint.Style.STROKE);
-        mFillPaint.setStrokeCap(Paint.Cap.ROUND);
-
-        mVibrator = mContext.getSystemService(Vibrator.class);
-
-        mProgressUpdateListener = animation -> {
-            mProgress = (float) animation.getAnimatedValue();
-            invalidateSelf();
-        };
-
-        mFillColorUpdateListener = animation -> {
-            mFillPaint.setColor((int) animation.getAnimatedValue());
-            invalidateSelf();
-        };
-
-        mCheckmarkUpdateListener = animation -> {
-            mCheckmarkScale = (float) animation.getAnimatedValue();
-            invalidateSelf();
-        };
-
-        mBackgroundColorUpdateListener = animation -> {
-            mBackgroundPaint.setColor((int) animation.getAnimatedValue());
-            invalidateSelf();
-        };
-    }
-
-    void loadResources(Context context, @Nullable AttributeSet attrs) {
-        final TypedArray ta = context.obtainStyledAttributes(attrs,
-                R.styleable.BiometricsEnrollView, R.attr.biometricsEnrollStyle,
-                R.style.BiometricsEnrollStyle);
-        mMovingTargetFill = ta.getColor(
-                R.styleable.BiometricsEnrollView_biometricsMovingTargetFill, 0);
-        mMovingTargetFillError = ta.getColor(
-                R.styleable.BiometricsEnrollView_biometricsMovingTargetFillError, 0);
-        mEnrollProgress = ta.getColor(
-                R.styleable.BiometricsEnrollView_biometricsEnrollProgress, 0);
-        mEnrollProgressHelp = ta.getColor(
-                R.styleable.BiometricsEnrollView_biometricsEnrollProgressHelp, 0);
-        mEnrollProgressHelpWithTalkback = ta.getColor(
-                R.styleable.BiometricsEnrollView_biometricsEnrollProgressHelpWithTalkback, 0);
-        ta.recycle();
-    }
-
-    void onEnrollmentProgress(int remaining, int totalSteps) {
-        mAfterFirstTouch = true;
-        updateState(remaining, totalSteps, false /* showingHelp */);
-    }
-
-    void onEnrollmentHelp(int remaining, int totalSteps) {
-        updateState(remaining, totalSteps, true /* showingHelp */);
-    }
-
-    void onLastStepAcquired() {
-        updateState(0, mTotalSteps, false /* showingHelp */);
-    }
-
-    private void updateState(int remainingSteps, int totalSteps, boolean showingHelp) {
-        updateProgress(remainingSteps, totalSteps, showingHelp);
-        updateFillColor(showingHelp);
-    }
-
-    private void updateProgress(int remainingSteps, int totalSteps, boolean showingHelp) {
-        if (mRemainingSteps == remainingSteps && mTotalSteps == totalSteps) {
-            return;
-        }
-
-        if (mShowingHelp) {
-            if (mVibrator != null && mIsAccessibilityEnabled) {
-                mVibrator.vibrate(Process.myUid(), mContext.getOpPackageName(),
-                        VIBRATE_EFFECT_ERROR, getClass().getSimpleName() + "::onEnrollmentHelp",
-                        FINGERPRINT_ENROLLING_SONFICATION_ATTRIBUTES);
-            }
-        } else {
-            // If the first touch is an error, remainingSteps will be -1 and the callback
-            // doesn't come from onEnrollmentHelp. If we are in the accessibility flow,
-            // we still would like to vibrate.
-            if (mVibrator != null) {
-                if (remainingSteps == -1 && mIsAccessibilityEnabled) {
-                    mVibrator.vibrate(Process.myUid(), mContext.getOpPackageName(),
-                            VIBRATE_EFFECT_ERROR,
-                            getClass().getSimpleName() + "::onFirstTouchError",
-                            FINGERPRINT_ENROLLING_SONFICATION_ATTRIBUTES);
-                } else if (remainingSteps != -1 && !mIsAccessibilityEnabled) {
-                    mVibrator.vibrate(Process.myUid(),
-                            mContext.getOpPackageName(),
-                            SUCCESS_VIBRATION_EFFECT,
-                            getClass().getSimpleName() + "::OnEnrollmentProgress",
-                            HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES);
-                }
-            }
-        }
-
-        mShowingHelp = showingHelp;
-        mRemainingSteps = remainingSteps;
-        mTotalSteps = totalSteps;
-
-        final int progressSteps = Math.max(0, totalSteps - remainingSteps);
-
-        // If needed, add 1 to progress and total steps to account for initial touch.
-        final int adjustedSteps = mAfterFirstTouch ? progressSteps + 1 : progressSteps;
-        final int adjustedTotal = mAfterFirstTouch ? mTotalSteps + 1 : mTotalSteps;
-
-        final float targetProgress = Math.min(1f, (float) adjustedSteps / (float) adjustedTotal);
-
-        if (mProgressAnimator != null && mProgressAnimator.isRunning()) {
-            mProgressAnimator.cancel();
-        }
-
-        mProgressAnimator = ValueAnimator.ofFloat(mProgress, targetProgress);
-        mProgressAnimator.setDuration(PROGRESS_ANIMATION_DURATION_MS);
-        mProgressAnimator.addUpdateListener(mProgressUpdateListener);
-        mProgressAnimator.start();
-
-        if (remainingSteps == 0) {
-            startCompletionAnimation();
-        } else if (remainingSteps > 0) {
-            rollBackCompletionAnimation();
-        }
-    }
-
-    private void animateBackgroundColor() {
-        if (mBackgroundColorAnimator != null && mBackgroundColorAnimator.isRunning()) {
-            mBackgroundColorAnimator.end();
-        }
-        mBackgroundColorAnimator = ValueAnimator.ofArgb(mBackgroundPaint.getColor(),
-                mOnFirstBucketFailedColor);
-        mBackgroundColorAnimator.setDuration(FILL_COLOR_ANIMATION_DURATION_MS);
-        mBackgroundColorAnimator.setRepeatCount(1);
-        mBackgroundColorAnimator.setRepeatMode(ValueAnimator.REVERSE);
-        mBackgroundColorAnimator.setInterpolator(DEACCEL);
-        mBackgroundColorAnimator.addUpdateListener(mBackgroundColorUpdateListener);
-        mBackgroundColorAnimator.start();
-    }
-
-    private void updateFillColor(boolean showingHelp) {
-        if (!mAfterFirstTouch && showingHelp) {
-            // If we are on the first touch, animate the background color
-            // instead of the progress color.
-            animateBackgroundColor();
-            return;
-        }
-
-        if (mFillColorAnimator != null && mFillColorAnimator.isRunning()) {
-            mFillColorAnimator.end();
-        }
-
-        @ColorInt final int targetColor = showingHelp ? mHelpColor : mProgressColor;
-        mFillColorAnimator = ValueAnimator.ofArgb(mFillPaint.getColor(), targetColor);
-        mFillColorAnimator.setDuration(FILL_COLOR_ANIMATION_DURATION_MS);
-        mFillColorAnimator.setRepeatCount(1);
-        mFillColorAnimator.setRepeatMode(ValueAnimator.REVERSE);
-        mFillColorAnimator.setInterpolator(DEACCEL);
-        mFillColorAnimator.addUpdateListener(mFillColorUpdateListener);
-        mFillColorAnimator.start();
-    }
-
-    private void startCompletionAnimation() {
-        if (mComplete) {
-            return;
-        }
-        mComplete = true;
-
-        if (mCheckmarkAnimator != null && mCheckmarkAnimator.isRunning()) {
-            mCheckmarkAnimator.cancel();
-        }
-
-        mCheckmarkAnimator = ValueAnimator.ofFloat(mCheckmarkScale, 1f);
-        mCheckmarkAnimator.setStartDelay(CHECKMARK_ANIMATION_DELAY_MS);
-        mCheckmarkAnimator.setDuration(CHECKMARK_ANIMATION_DURATION_MS);
-        mCheckmarkAnimator.setInterpolator(mCheckmarkInterpolator);
-        mCheckmarkAnimator.addUpdateListener(mCheckmarkUpdateListener);
-        mCheckmarkAnimator.start();
-    }
-
-    private void rollBackCompletionAnimation() {
-        if (!mComplete) {
-            return;
-        }
-        mComplete = false;
-
-        // Adjust duration based on how much of the completion animation has played.
-        final float animatedFraction = mCheckmarkAnimator != null
-                ? mCheckmarkAnimator.getAnimatedFraction()
-                : 0f;
-        final long durationMs = Math.round(CHECKMARK_ANIMATION_DELAY_MS * animatedFraction);
-
-        if (mCheckmarkAnimator != null && mCheckmarkAnimator.isRunning()) {
-            mCheckmarkAnimator.cancel();
-        }
-
-        mCheckmarkAnimator = ValueAnimator.ofFloat(mCheckmarkScale, 0f);
-        mCheckmarkAnimator.setDuration(durationMs);
-        mCheckmarkAnimator.addUpdateListener(mCheckmarkUpdateListener);
-        mCheckmarkAnimator.start();
-    }
-
-    @Override
-    public void draw(@NonNull Canvas canvas) {
-        canvas.save();
-
-        // Progress starts from the top, instead of the right
-        canvas.rotate(-90f, getBounds().centerX(), getBounds().centerY());
-
-        final float halfPaddingPx = mStrokeWidthPx / 2f;
-
-        if (mProgress < 1f) {
-            // Draw the background color of the progress circle.
-            canvas.drawArc(
-                    halfPaddingPx,
-                    halfPaddingPx,
-                    getBounds().right - halfPaddingPx,
-                    getBounds().bottom - halfPaddingPx,
-                    0f /* startAngle */,
-                    360f /* sweepAngle */,
-                    false /* useCenter */,
-                    mBackgroundPaint);
-        }
-
-        if (mProgress > 0f) {
-            // Draw the filled portion of the progress circle.
-            canvas.drawArc(
-                    halfPaddingPx,
-                    halfPaddingPx,
-                    getBounds().right - halfPaddingPx,
-                    getBounds().bottom - halfPaddingPx,
-                    0f /* startAngle */,
-                    360f * mProgress /* sweepAngle */,
-                    false /* useCenter */,
-                    mFillPaint);
-        }
-
-        canvas.restore();
-
-        if (mCheckmarkScale > 0f) {
-            final float offsetScale = (float) Math.sqrt(2) / 2f;
-            final float centerXOffset = (getBounds().width() - mStrokeWidthPx) / 2f * offsetScale;
-            final float centerYOffset = (getBounds().height() - mStrokeWidthPx) / 2f * offsetScale;
-            final float centerX = getBounds().centerX() + centerXOffset;
-            final float centerY = getBounds().centerY() + centerYOffset;
-
-            final float boundsXOffset =
-                    mCheckmarkDrawable.getIntrinsicWidth() / 2f * mCheckmarkScale;
-            final float boundsYOffset =
-                    mCheckmarkDrawable.getIntrinsicHeight() / 2f * mCheckmarkScale;
-
-            final int left = Math.round(centerX - boundsXOffset);
-            final int top = Math.round(centerY - boundsYOffset);
-            final int right = Math.round(centerX + boundsXOffset);
-            final int bottom = Math.round(centerY + boundsYOffset);
-            mCheckmarkDrawable.setBounds(left, top, right, bottom);
-            mCheckmarkDrawable.draw(canvas);
-        }
-    }
-
-    @Override
-    public void setAlpha(int alpha) {
-    }
-
-    @Override
-    public void setColorFilter(@Nullable ColorFilter colorFilter) {
-    }
-
-    @Override
-    public int getOpacity() {
-        return 0;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java
deleted file mode 100644
index 1cc4141..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.biometrics;
-
-import android.content.Context;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.os.Handler;
-import android.os.Looper;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageView;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.systemui.R;
-
-/**
- * View corresponding with udfps_enroll_view.xml
- */
-public class UdfpsEnrollView extends UdfpsAnimationView {
-    @NonNull private final UdfpsEnrollDrawable mFingerprintDrawable;
-    @NonNull private final UdfpsEnrollProgressBarDrawable mFingerprintProgressDrawable;
-    @NonNull private final Handler mHandler;
-
-    @NonNull private ImageView mFingerprintView;
-    @NonNull private ImageView mFingerprintProgressView;
-
-    private LayoutParams mProgressParams;
-    private float mProgressBarRadius;
-
-    public UdfpsEnrollView(Context context, @Nullable AttributeSet attrs) {
-        super(context, attrs);
-        mFingerprintDrawable = new UdfpsEnrollDrawable(mContext, attrs);
-        mFingerprintProgressDrawable = new UdfpsEnrollProgressBarDrawable(context, attrs);
-        mHandler = new Handler(Looper.getMainLooper());
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        mFingerprintView = findViewById(R.id.udfps_enroll_animation_fp_view);
-        mFingerprintProgressView = findViewById(R.id.udfps_enroll_animation_fp_progress_view);
-        mFingerprintView.setImageDrawable(mFingerprintDrawable);
-        mFingerprintProgressView.setImageDrawable(mFingerprintProgressDrawable);
-    }
-
-    @Override
-    void onSensorRectUpdated(RectF bounds) {
-        if (mUseExpandedOverlay) {
-            RectF converted = getBoundsRelativeToView(bounds);
-
-            mProgressParams = new LayoutParams(
-                    (int) (converted.width() + mProgressBarRadius * 2),
-                    (int) (converted.height() + mProgressBarRadius * 2));
-            mProgressParams.setMargins(
-                    (int) (converted.left - mProgressBarRadius),
-                    (int) (converted.top - mProgressBarRadius),
-                    (int) (converted.right + mProgressBarRadius),
-                    (int) (converted.bottom + mProgressBarRadius)
-            );
-
-            mFingerprintProgressView.setLayoutParams(mProgressParams);
-            super.onSensorRectUpdated(converted);
-        } else {
-            super.onSensorRectUpdated(bounds);
-        }
-    }
-
-    void setProgressBarRadius(float radius) {
-        mProgressBarRadius = radius;
-    }
-
-    @Override
-    public UdfpsDrawable getDrawable() {
-        return mFingerprintDrawable;
-    }
-
-    void updateSensorLocation(@NonNull Rect sensorBounds) {
-        View fingerprintAccessibilityView = findViewById(R.id.udfps_enroll_accessibility_view);
-        ViewGroup.LayoutParams params = fingerprintAccessibilityView.getLayoutParams();
-        params.width = sensorBounds.width();
-        params.height = sensorBounds.height();
-        fingerprintAccessibilityView.setLayoutParams(params);
-        fingerprintAccessibilityView.requestLayout();
-    }
-
-    void setEnrollHelper(UdfpsEnrollHelper enrollHelper) {
-        mFingerprintDrawable.setEnrollHelper(enrollHelper);
-    }
-
-    void onEnrollmentProgress(int remaining, int totalSteps) {
-        mHandler.post(() -> {
-            mFingerprintProgressDrawable.onEnrollmentProgress(remaining, totalSteps);
-            mFingerprintDrawable.onEnrollmentProgress(remaining, totalSteps);
-        });
-    }
-
-    void onEnrollmentHelp(int remaining, int totalSteps) {
-        mHandler.post(() -> mFingerprintProgressDrawable.onEnrollmentHelp(remaining, totalSteps));
-    }
-
-    void onLastStepAcquired() {
-        mHandler.post(mFingerprintProgressDrawable::onLastStepAcquired);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java
deleted file mode 100644
index c82e6e1..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.biometrics;
-
-import android.annotation.NonNull;
-import android.graphics.PointF;
-
-import com.android.systemui.R;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shade.ShadeExpansionStateManager;
-import com.android.systemui.statusbar.phone.SystemUIDialogManager;
-
-/**
- * Class that coordinates non-HBM animations during enrollment.
- */
-public class UdfpsEnrollViewController extends UdfpsAnimationViewController<UdfpsEnrollView> {
-
-    private final int mEnrollProgressBarRadius;
-    @NonNull private final UdfpsEnrollHelper mEnrollHelper;
-    @NonNull private final UdfpsEnrollHelper.Listener mEnrollHelperListener =
-            new UdfpsEnrollHelper.Listener() {
-                @Override
-                public void onEnrollmentProgress(int remaining, int totalSteps) {
-                    mView.onEnrollmentProgress(remaining, totalSteps);
-                }
-
-                @Override
-                public void onEnrollmentHelp(int remaining, int totalSteps) {
-                    mView.onEnrollmentHelp(remaining, totalSteps);
-                }
-
-                @Override
-                public void onLastStepAcquired() {
-                    mView.onLastStepAcquired();
-                }
-            };
-
-    protected UdfpsEnrollViewController(
-            @NonNull UdfpsEnrollView view,
-            @NonNull UdfpsEnrollHelper enrollHelper,
-            @NonNull StatusBarStateController statusBarStateController,
-            @NonNull ShadeExpansionStateManager shadeExpansionStateManager,
-            @NonNull SystemUIDialogManager systemUIDialogManager,
-            @NonNull DumpManager dumpManager,
-            @NonNull FeatureFlags featureFlags,
-            float scaleFactor) {
-        super(view, statusBarStateController, shadeExpansionStateManager, systemUIDialogManager,
-                dumpManager);
-        mEnrollProgressBarRadius = (int) (scaleFactor * getContext().getResources().getInteger(
-                R.integer.config_udfpsEnrollProgressBar));
-        mEnrollHelper = enrollHelper;
-        mView.setEnrollHelper(mEnrollHelper);
-        mView.setProgressBarRadius(mEnrollProgressBarRadius);
-        mView.mUseExpandedOverlay = featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION);
-    }
-
-    @Override
-    @NonNull protected String getTag() {
-        return "UdfpsEnrollViewController";
-    }
-
-    @Override
-    protected void onViewAttached() {
-        super.onViewAttached();
-        if (mEnrollHelper.shouldShowProgressBar()) {
-            // Only need enrollment updates if the progress bar is showing :)
-            mEnrollHelper.setListener(mEnrollHelperListener);
-        }
-    }
-
-    @NonNull
-    @Override
-    public PointF getTouchTranslation() {
-        if (!mEnrollHelper.isGuidedEnrollmentStage()) {
-            return new PointF(0, 0);
-        } else {
-            return mEnrollHelper.getNextGuidedEnrollmentPoint();
-        }
-    }
-
-    @Override
-    public int getPaddingX() {
-        return mEnrollProgressBarRadius;
-    }
-
-    @Override
-    public int getPaddingY() {
-        return mEnrollProgressBarRadius;
-    }
-
-    @Override
-    public void doAnnounceForAccessibility(String str) {
-        mView.announceForAccessibility(str);
-    }
-
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessor.kt
index 234b383..6c9390d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessor.kt
@@ -92,9 +92,14 @@
         val data = touch.data.find { it.pointerId == pointerOnSensorId } ?: NormalizedTouchData()
         ProcessedTouch(InteractionEvent.DOWN, data.pointerId, data)
     } else if (hadPointerOnSensor && !hasPointerOnSensor) {
-        ProcessedTouch(InteractionEvent.UP, INVALID_POINTER_ID, NormalizedTouchData())
+        val data =
+            touch.data.find { it.pointerId == touch.previousPointerOnSensorId }
+                ?: NormalizedTouchData()
+        ProcessedTouch(InteractionEvent.UP, INVALID_POINTER_ID, data)
     } else {
-        val data = touch.data.find { it.pointerId == pointerOnSensorId } ?: NormalizedTouchData()
+        val data =
+            touch.data.find { it.pointerId == pointerOnSensorId }
+                ?: touch.data.firstOrNull() ?: NormalizedTouchData()
         ProcessedTouch(InteractionEvent.UNCHANGED, pointerOnSensorId, data)
     }
 }
@@ -102,16 +107,15 @@
 private fun processActionUp(touch: PreprocessedTouch, actionId: Int): TouchProcessorResult {
     // Finger lifted and it was the only finger on the sensor
     return if (touch.pointersOnSensor.size == 1 && touch.pointersOnSensor.contains(actionId)) {
-        ProcessedTouch(
-            InteractionEvent.UP,
-            pointerOnSensorId = INVALID_POINTER_ID,
-            NormalizedTouchData()
-        )
+        val data = touch.data.find { it.pointerId == actionId } ?: NormalizedTouchData()
+        ProcessedTouch(InteractionEvent.UP, pointerOnSensorId = INVALID_POINTER_ID, data)
     } else {
         // Pick new pointerOnSensor that's not the finger that was lifted
         val pointerOnSensorId = touch.pointersOnSensor.find { it != actionId } ?: INVALID_POINTER_ID
-        val data = touch.data.find { it.pointerId == pointerOnSensorId } ?: NormalizedTouchData()
-        ProcessedTouch(InteractionEvent.UNCHANGED, data.pointerId, data)
+        val data =
+            touch.data.find { it.pointerId == pointerOnSensorId }
+                ?: touch.data.firstOrNull() ?: NormalizedTouchData()
+        ProcessedTouch(InteractionEvent.UNCHANGED, pointerOnSensorId, data)
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt
index 27466d4..7509a8a 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt
@@ -19,11 +19,11 @@
 import android.content.Context
 import com.android.internal.widget.LockPatternUtils
 import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
-import com.android.systemui.controls.settings.ControlsSettingsRepository
 import com.android.systemui.controls.controller.ControlsController
 import com.android.systemui.controls.controller.ControlsTileResourceConfiguration
 import com.android.systemui.controls.controller.ControlsTileResourceConfigurationImpl
 import com.android.systemui.controls.management.ControlsListingController
+import com.android.systemui.controls.settings.ControlsSettingsRepository
 import com.android.systemui.controls.ui.ControlsUiController
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.settings.UserTracker
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamMonitor.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamMonitor.java
index 102f208..055cd52 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamMonitor.java
@@ -16,19 +16,23 @@
 
 package com.android.systemui.dreams;
 
+import static com.android.systemui.dreams.dagger.DreamModule.DREAM_PRETEXT_MONITOR;
+
 import android.util.Log;
 
 import com.android.systemui.CoreStartable;
 import com.android.systemui.dreams.callbacks.DreamStatusBarStateCallback;
 import com.android.systemui.dreams.conditions.DreamCondition;
 import com.android.systemui.shared.condition.Monitor;
+import com.android.systemui.util.condition.ConditionalCoreStartable;
 
 import javax.inject.Inject;
+import javax.inject.Named;
 
 /**
  * A {@link CoreStartable} to retain a monitor for tracking dreaming.
  */
-public class DreamMonitor implements CoreStartable {
+public class DreamMonitor extends ConditionalCoreStartable {
     private static final String TAG = "DreamMonitor";
 
     // We retain a reference to the monitor so it is not garbage-collected.
@@ -39,14 +43,17 @@
 
     @Inject
     public DreamMonitor(Monitor monitor, DreamCondition dreamCondition,
+            @Named(DREAM_PRETEXT_MONITOR) Monitor pretextMonitor,
             DreamStatusBarStateCallback callback) {
+        super(pretextMonitor);
         mConditionMonitor = monitor;
         mDreamCondition = dreamCondition;
         mCallback = callback;
 
     }
+
     @Override
-    public void start() {
+    protected void onStart() {
         if (Log.isLoggable(TAG, Log.DEBUG)) {
             Log.d(TAG, "started");
         }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayRegistrant.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayRegistrant.java
index 87c5f51..a2dcdf5 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayRegistrant.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayRegistrant.java
@@ -17,6 +17,7 @@
 package com.android.systemui.dreams;
 
 import static com.android.systemui.dreams.dagger.DreamModule.DREAM_OVERLAY_SERVICE_COMPONENT;
+import static com.android.systemui.dreams.dagger.DreamModule.DREAM_PRETEXT_MONITOR;
 
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -33,8 +34,9 @@
 import android.service.dreams.IDreamManager;
 import android.util.Log;
 
-import com.android.systemui.CoreStartable;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.shared.condition.Monitor;
+import com.android.systemui.util.condition.ConditionalCoreStartable;
 
 import javax.inject.Inject;
 import javax.inject.Named;
@@ -43,7 +45,7 @@
  * {@link DreamOverlayRegistrant} is responsible for telling system server that SystemUI should be
  * the designated dream overlay component.
  */
-public class DreamOverlayRegistrant implements CoreStartable {
+public class DreamOverlayRegistrant extends ConditionalCoreStartable {
     private static final String TAG = "DreamOverlayRegistrant";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
     private final IDreamManager mDreamManager;
@@ -102,7 +104,9 @@
 
     @Inject
     public DreamOverlayRegistrant(Context context, @Main Resources resources,
-            @Named(DREAM_OVERLAY_SERVICE_COMPONENT) ComponentName dreamOverlayServiceComponent) {
+            @Named(DREAM_OVERLAY_SERVICE_COMPONENT) ComponentName dreamOverlayServiceComponent,
+            @Named(DREAM_PRETEXT_MONITOR) Monitor monitor) {
+        super(monitor);
         mContext = context;
         mResources = resources;
         mDreamManager = IDreamManager.Stub.asInterface(
@@ -111,7 +115,7 @@
     }
 
     @Override
-    public void start() {
+    protected void onStart() {
         final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_CHANGED);
         filter.addDataScheme("package");
         filter.addDataSchemeSpecificPart(mOverlayServiceComponent.getPackageName(),
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationTypesUpdater.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationTypesUpdater.java
index ee2f1af..244212b 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationTypesUpdater.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationTypesUpdater.java
@@ -16,27 +16,31 @@
 
 package com.android.systemui.dreams.complication;
 
+import static com.android.systemui.dreams.dagger.DreamModule.DREAM_PRETEXT_MONITOR;
+
 import android.database.ContentObserver;
 import android.os.UserHandle;
 import android.provider.Settings;
 
 import com.android.settingslib.dream.DreamBackend;
-import com.android.systemui.CoreStartable;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.shared.condition.Monitor;
+import com.android.systemui.util.condition.ConditionalCoreStartable;
 import com.android.systemui.util.settings.SecureSettings;
 
 import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
+import javax.inject.Named;
 
 /**
  * {@link ComplicationTypesUpdater} observes the state of available complication types set by the
  * user, and pushes updates to {@link DreamOverlayStateController}.
  */
 @SysUISingleton
-public class ComplicationTypesUpdater implements CoreStartable {
+public class ComplicationTypesUpdater extends ConditionalCoreStartable {
     private final DreamBackend mDreamBackend;
     private final Executor mExecutor;
     private final SecureSettings mSecureSettings;
@@ -48,7 +52,9 @@
             DreamBackend dreamBackend,
             @Main Executor executor,
             SecureSettings secureSettings,
-            DreamOverlayStateController dreamOverlayStateController) {
+            DreamOverlayStateController dreamOverlayStateController,
+            @Named(DREAM_PRETEXT_MONITOR) Monitor monitor) {
+        super(monitor);
         mDreamBackend = dreamBackend;
         mExecutor = executor;
         mSecureSettings = secureSettings;
@@ -56,7 +62,7 @@
     }
 
     @Override
-    public void start() {
+    public void onStart() {
         final ContentObserver settingsObserver = new ContentObserver(null /*handler*/) {
             @Override
             public void onChange(boolean selfChange) {
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockTimeComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockTimeComplication.java
index 77e1fc9..bb1e6e2 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockTimeComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockTimeComplication.java
@@ -18,11 +18,14 @@
 
 import static com.android.systemui.dreams.complication.dagger.DreamClockTimeComplicationModule.DREAM_CLOCK_TIME_COMPLICATION_VIEW;
 import static com.android.systemui.dreams.complication.dagger.RegisteredComplicationsModule.DREAM_CLOCK_TIME_COMPLICATION_LAYOUT_PARAMS;
+import static com.android.systemui.dreams.dagger.DreamModule.DREAM_PRETEXT_MONITOR;
 
 import android.view.View;
 
 import com.android.systemui.CoreStartable;
 import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.shared.condition.Monitor;
+import com.android.systemui.util.condition.ConditionalCoreStartable;
 
 import javax.inject.Inject;
 import javax.inject.Named;
@@ -60,7 +63,7 @@
      * {@link CoreStartable} responsible for registering {@link DreamClockTimeComplication} with
      * SystemUI.
      */
-    public static class Registrant implements CoreStartable {
+    public static class Registrant extends ConditionalCoreStartable {
         private final DreamOverlayStateController mDreamOverlayStateController;
         private final DreamClockTimeComplication mComplication;
 
@@ -70,13 +73,15 @@
         @Inject
         public Registrant(
                 DreamOverlayStateController dreamOverlayStateController,
-                DreamClockTimeComplication dreamClockTimeComplication) {
+                DreamClockTimeComplication dreamClockTimeComplication,
+                @Named(DREAM_PRETEXT_MONITOR) Monitor monitor) {
+            super(monitor);
             mDreamOverlayStateController = dreamOverlayStateController;
             mComplication = dreamClockTimeComplication;
         }
 
         @Override
-        public void start() {
+        public void onStart() {
             mDreamOverlayStateController.addComplication(mComplication);
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
index 1065b94..7f395d8 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
@@ -21,6 +21,7 @@
 import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.UNAVAILABLE;
 import static com.android.systemui.dreams.complication.dagger.DreamHomeControlsComplicationComponent.DreamHomeControlsModule.DREAM_HOME_CONTROLS_CHIP_VIEW;
 import static com.android.systemui.dreams.complication.dagger.RegisteredComplicationsModule.DREAM_HOME_CONTROLS_CHIP_LAYOUT_PARAMS;
+import static com.android.systemui.dreams.dagger.DreamModule.DREAM_PRETEXT_MONITOR;
 
 import android.content.Context;
 import android.content.Intent;
@@ -42,7 +43,9 @@
 import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.dreams.complication.dagger.DreamHomeControlsComplicationComponent;
 import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.shared.condition.Monitor;
 import com.android.systemui.util.ViewController;
+import com.android.systemui.util.condition.ConditionalCoreStartable;
 
 import java.util.List;
 
@@ -75,7 +78,7 @@
     /**
      * {@link CoreStartable} for registering the complication with SystemUI on startup.
      */
-    public static class Registrant implements CoreStartable {
+    public static class Registrant extends ConditionalCoreStartable {
         private final DreamHomeControlsComplication mComplication;
         private final DreamOverlayStateController mDreamOverlayStateController;
         private final ControlsComponent mControlsComponent;
@@ -105,14 +108,16 @@
         @Inject
         public Registrant(DreamHomeControlsComplication complication,
                 DreamOverlayStateController dreamOverlayStateController,
-                ControlsComponent controlsComponent) {
+                ControlsComponent controlsComponent,
+                @Named(DREAM_PRETEXT_MONITOR) Monitor monitor) {
+            super(monitor);
             mComplication = complication;
             mControlsComponent = controlsComponent;
             mDreamOverlayStateController = dreamOverlayStateController;
         }
 
         @Override
-        public void start() {
+        public void onStart() {
             mControlsComponent.getControlsListingController().ifPresent(
                     c -> c.addCallback(mControlsCallback));
             mDreamOverlayStateController.addCallback(mOverlayStateCallback);
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/SmartSpaceComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/SmartSpaceComplication.java
index c3aaf0c..e39073b 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/SmartSpaceComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/SmartSpaceComplication.java
@@ -17,6 +17,7 @@
 package com.android.systemui.dreams.complication;
 
 import static com.android.systemui.dreams.complication.dagger.RegisteredComplicationsModule.DREAM_SMARTSPACE_LAYOUT_PARAMS;
+import static com.android.systemui.dreams.dagger.DreamModule.DREAM_PRETEXT_MONITOR;
 
 import android.content.Context;
 import android.os.Parcelable;
@@ -28,6 +29,8 @@
 import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.dreams.smartspace.DreamSmartspaceController;
 import com.android.systemui.plugins.BcSmartspaceDataPlugin;
+import com.android.systemui.shared.condition.Monitor;
+import com.android.systemui.util.condition.ConditionalCoreStartable;
 
 import java.util.List;
 
@@ -61,7 +64,7 @@
      * {@link CoreStartable} responsbile for registering {@link SmartSpaceComplication} with
      * SystemUI.
      */
-    public static class Registrant implements CoreStartable {
+    public static class Registrant extends ConditionalCoreStartable {
         private final DreamSmartspaceController mSmartSpaceController;
         private final DreamOverlayStateController mDreamOverlayStateController;
         private final SmartSpaceComplication mComplication;
@@ -81,14 +84,16 @@
         public Registrant(
                 DreamOverlayStateController dreamOverlayStateController,
                 SmartSpaceComplication smartSpaceComplication,
-                DreamSmartspaceController smartSpaceController) {
+                DreamSmartspaceController smartSpaceController,
+                @Named(DREAM_PRETEXT_MONITOR) Monitor monitor) {
+            super(monitor);
             mDreamOverlayStateController = dreamOverlayStateController;
             mComplication = smartSpaceComplication;
             mSmartSpaceController = smartSpaceController;
         }
 
         @Override
-        public void start() {
+        public void onStart() {
             mDreamOverlayStateController.addCallback(new DreamOverlayStateController.Callback() {
                 @Override
                 public void onStateChanged() {
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
index 7d8389a..3a37c6f 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
@@ -30,11 +30,18 @@
 import com.android.systemui.dreams.complication.dagger.RegisteredComplicationsModule;
 import com.android.systemui.dreams.dreamcomplication.dagger.ComplicationComponent;
 import com.android.systemui.dreams.touch.scrim.dagger.ScrimModule;
+import com.android.systemui.process.condition.UserProcessCondition;
+import com.android.systemui.shared.condition.Condition;
+import com.android.systemui.shared.condition.Monitor;
 
+import dagger.Binds;
 import dagger.Module;
 import dagger.Provides;
+import dagger.multibindings.IntoSet;
 
 import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.Executor;
 
 import javax.inject.Named;
 
@@ -57,6 +64,8 @@
     String DREAM_OVERLAY_ENABLED = "dream_overlay_enabled";
 
     String DREAM_SUPPORTED = "dream_supported";
+    String DREAM_PRETEXT_CONDITIONS = "dream_pretext_conditions";
+    String DREAM_PRETEXT_MONITOR = "dream_prtext_monitor";
 
     /**
      * Provides the dream component
@@ -115,4 +124,19 @@
     static boolean providesDreamSupported(@Main Resources resources) {
         return resources.getBoolean(com.android.internal.R.bool.config_dreamsSupported);
     }
+
+    /** */
+    @Binds
+    @IntoSet
+    @Named(DREAM_PRETEXT_CONDITIONS)
+    Condition bindsUserProcessCondition(UserProcessCondition condition);
+
+    /** */
+    @Provides
+    @Named(DREAM_PRETEXT_MONITOR)
+    static Monitor providesDockerPretextMonitor(
+            @Main Executor executor,
+            @Named(DREAM_PRETEXT_CONDITIONS) Set<Condition> pretextConditions) {
+        return new Monitor(executor, pretextConditions);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
index 9b2e6b8..d3fe2c5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
@@ -24,6 +24,7 @@
 
 import androidx.annotation.IntDef;
 
+import com.android.keyguard.logging.KeyguardLogger;
 import com.android.systemui.Dumpable;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -64,6 +65,7 @@
             2000L + KeyguardIndicationTextView.Y_IN_DURATION;
 
     private final StatusBarStateController mStatusBarStateController;
+    private final KeyguardLogger mLogger;
     private final float mMaxAlpha;
     private final ColorStateList mInitialTextColorState;
 
@@ -85,7 +87,8 @@
     public KeyguardIndicationRotateTextViewController(
             KeyguardIndicationTextView view,
             @Main DelayableExecutor executor,
-            StatusBarStateController statusBarStateController
+            StatusBarStateController statusBarStateController,
+            KeyguardLogger logger
     ) {
         super(view);
         mMaxAlpha = view.getAlpha();
@@ -93,6 +96,7 @@
         mInitialTextColorState = mView != null
                 ? mView.getTextColors() : ColorStateList.valueOf(Color.WHITE);
         mStatusBarStateController = statusBarStateController;
+        mLogger = logger;
         init();
     }
 
@@ -259,6 +263,8 @@
         mLastIndicationSwitch = SystemClock.uptimeMillis();
         if (!TextUtils.equals(previousMessage, mCurrMessage)
                 || previousIndicationType != mCurrIndicationType) {
+            mLogger.logKeyguardSwitchIndication(type,
+                    mCurrMessage != null ? mCurrMessage.toString() : null);
             mView.switchIndication(mIndicationMessages.get(type));
         }
 
@@ -352,9 +358,10 @@
     @Override
     public void dump(PrintWriter pw, String[] args) {
         pw.println("KeyguardIndicationRotatingTextViewController:");
-        pw.println("    currentMessage=" + mView.getText());
+        pw.println("    currentTextViewMessage=" + mView.getText());
+        pw.println("    currentStoredMessage=" + mView.getMessage());
         pw.println("    dozing:" + mIsDozing);
-        pw.println("    queue:" + mIndicationQueue.toString());
+        pw.println("    queue:" + mIndicationQueue);
         pw.println("    showNextIndicationRunnable:" + mShowNextIndicationRunnable);
 
         if (hasIndications()) {
@@ -398,4 +405,40 @@
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface IndicationType{}
+
+    /**
+     * Get human-readable string representation of the indication type.
+     */
+    public static String indicationTypeToString(@IndicationType int type) {
+        switch (type) {
+            case INDICATION_TYPE_NONE:
+                return "none";
+            case INDICATION_TYPE_DISCLOSURE:
+                return "disclosure";
+            case INDICATION_TYPE_OWNER_INFO:
+                return "owner_info";
+            case INDICATION_TYPE_LOGOUT:
+                return "logout";
+            case INDICATION_TYPE_BATTERY:
+                return "battery";
+            case INDICATION_TYPE_ALIGNMENT:
+                return "alignment";
+            case INDICATION_TYPE_TRANSIENT:
+                return "transient";
+            case INDICATION_TYPE_TRUST:
+                return "trust";
+            case INDICATION_TYPE_PERSISTENT_UNLOCK_MESSAGE:
+                return "persistent_unlock_message";
+            case INDICATION_TYPE_USER_LOCKED:
+                return "user_locked";
+            case INDICATION_TYPE_REVERSE_CHARGING:
+                return "reverse_charging";
+            case INDICATION_TYPE_BIOMETRIC_MESSAGE:
+                return "biometric_message";
+            case INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP:
+                return "biometric_message_followup";
+            default:
+                return "unknown[" + type + "]";
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 0dbc930..c5ea241 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -2442,15 +2442,28 @@
         }
         mKeyguardDisplayManager.show();
 
-        // schedule 4hr idle timeout after which non-strong biometrics (i.e. weak or convenience
-        // biometric) can't be used to unlock device until unlocking with strong biometric or
-        // primary auth (i.e. PIN/pattern/password)
-        mLockPatternUtils.scheduleNonStrongBiometricIdleTimeout(
-                KeyguardUpdateMonitor.getCurrentUser());
+        scheduleNonStrongBiometricIdleTimeout();
 
         Trace.endSection();
     }
 
+    /**
+     * Schedule 4-hour idle timeout for non-strong biometrics when the device is locked
+     */
+    private void scheduleNonStrongBiometricIdleTimeout() {
+        final int currentUser = KeyguardUpdateMonitor.getCurrentUser();
+        // If unlocking with non-strong (i.e. weak or convenience) biometrics is possible, schedule
+        // 4hr idle timeout after which non-strong biometrics can't be used to unlock device until
+        // unlocking with strong biometric or primary auth (i.e. PIN/pattern/password)
+        if (mUpdateMonitor.isUnlockingWithNonStrongBiometricsPossible(currentUser)) {
+            if (DEBUG) {
+                Log.d(TAG, "scheduleNonStrongBiometricIdleTimeout: schedule an alarm for "
+                        + "currentUser=" + currentUser);
+            }
+            mLockPatternUtils.scheduleNonStrongBiometricIdleTimeout(currentUser);
+        }
+    }
+
     private final Runnable mKeyguardGoingAwayRunnable = new Runnable() {
         @Override
         public void run() {
@@ -2934,6 +2947,8 @@
             if (DEBUG) Log.d(TAG, "handleReset");
             mKeyguardViewControllerLazy.get().reset(true /* hideBouncerWhenShowing */);
         }
+
+        scheduleNonStrongBiometricIdleTimeout();
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
index 81a5828..8715d1f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
@@ -34,6 +34,7 @@
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.launch
 
@@ -56,9 +57,14 @@
 
     private fun listenForDreamingToLockscreen() {
         scope.launch {
-            // Using isDreamingWithOverlay provides an optimized path to LOCKSCREEN state, which
-            // otherwise would have gone through OCCLUDED first
-            keyguardInteractor.isAbleToDream
+            // Dependending on the dream, either dream state or occluded change will change first,
+            // so listen for both
+            combine(keyguardInteractor.isAbleToDream, keyguardInteractor.isKeyguardOccluded) {
+                    isAbleToDream,
+                    isKeyguardOccluded ->
+                    isAbleToDream && isKeyguardOccluded
+                }
+                .distinctUntilChanged()
                 .sample(
                     combine(
                         keyguardInteractor.dozeTransitionModel,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index adde595..403576c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -38,6 +38,7 @@
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel
 import com.android.systemui.shared.clocks.ClockRegistry
+import com.android.systemui.shared.clocks.shared.model.ClockPreviewConstants
 import com.android.systemui.shared.quickaffordance.shared.model.KeyguardQuickAffordancePreviewConstants
 import com.android.systemui.statusbar.phone.KeyguardBottomAreaView
 import dagger.assisted.Assisted
@@ -69,6 +70,8 @@
             KeyguardQuickAffordancePreviewConstants.KEY_HIGHLIGHT_QUICK_AFFORDANCES,
             false,
         )
+    private val shouldHideClock: Boolean =
+        bundle.getBoolean(ClockPreviewConstants.KEY_HIDE_CLOCK, false)
 
     private var host: SurfaceControlViewHost
 
@@ -104,7 +107,9 @@
             val rootView = FrameLayout(context)
 
             setUpBottomArea(rootView)
-            setUpClock(rootView)
+            if (!shouldHideClock) {
+                setUpClock(rootView)
+            }
 
             rootView.measure(
                 View.MeasureSpec.makeMeasureSpec(
diff --git a/packages/SystemUI/src/com/android/systemui/log/table/Diffable.kt b/packages/SystemUI/src/com/android/systemui/log/table/Diffable.kt
index 348d941..ccd4060 100644
--- a/packages/SystemUI/src/com/android/systemui/log/table/Diffable.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/table/Diffable.kt
@@ -79,10 +79,10 @@
     }
 }
 
-/**
- * Each time the boolean flow is updated with a new value that's different from the previous value,
- * logs the new value to the given [tableLogBuffer].
- */
+// Here and below: Various Flow<SomeType> extension functions that are effectively equivalent to the
+// above [logDiffsForTable] method.
+
+/** See [logDiffsForTable(TableLogBuffer, String, T)]. */
 fun Flow<Boolean>.logDiffsForTable(
     tableLogBuffer: TableLogBuffer,
     columnPrefix: String,
@@ -100,10 +100,8 @@
         newVal
     }
 }
-/**
- * Each time the Int flow is updated with a new value that's different from the previous value, logs
- * the new value to the given [tableLogBuffer].
- */
+
+/** See [logDiffsForTable(TableLogBuffer, String, T)]. */
 fun Flow<Int>.logDiffsForTable(
     tableLogBuffer: TableLogBuffer,
     columnPrefix: String,
@@ -122,10 +120,26 @@
     }
 }
 
-/**
- * Each time the String? flow is updated with a new value that's different from the previous value,
- * logs the new value to the given [tableLogBuffer].
- */
+/** See [logDiffsForTable(TableLogBuffer, String, T)]. */
+fun Flow<Int?>.logDiffsForTable(
+    tableLogBuffer: TableLogBuffer,
+    columnPrefix: String,
+    columnName: String,
+    initialValue: Int?,
+): Flow<Int?> {
+    val initialValueFun = {
+        tableLogBuffer.logChange(columnPrefix, columnName, initialValue)
+        initialValue
+    }
+    return this.pairwiseBy(initialValueFun) { prevVal, newVal: Int? ->
+        if (prevVal != newVal) {
+            tableLogBuffer.logChange(columnPrefix, columnName, newVal)
+        }
+        newVal
+    }
+}
+
+/** See [logDiffsForTable(TableLogBuffer, String, T)]. */
 fun Flow<String?>.logDiffsForTable(
     tableLogBuffer: TableLogBuffer,
     columnPrefix: String,
@@ -143,3 +157,23 @@
         newVal
     }
 }
+
+/** See [logDiffsForTable(TableLogBuffer, String, T)]. */
+fun <T> Flow<List<T>>.logDiffsForTable(
+    tableLogBuffer: TableLogBuffer,
+    columnPrefix: String,
+    columnName: String,
+    initialValue: List<T>,
+): Flow<List<T>> {
+    val initialValueFun = {
+        tableLogBuffer.logChange(columnPrefix, columnName, initialValue.toString())
+        initialValue
+    }
+    return this.pairwiseBy(initialValueFun) { prevVal, newVal: List<T> ->
+        if (prevVal != newVal) {
+            // TODO(b/267761156): Can we log list changes without using toString?
+            tableLogBuffer.logChange(columnPrefix, columnName, newVal.toString())
+        }
+        newVal
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/log/table/TableChange.kt b/packages/SystemUI/src/com/android/systemui/log/table/TableChange.kt
index 68c297f..4880f80 100644
--- a/packages/SystemUI/src/com/android/systemui/log/table/TableChange.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/table/TableChange.kt
@@ -27,7 +27,7 @@
     var columnName: String = "",
     var type: DataType = DataType.EMPTY,
     var bool: Boolean = false,
-    var int: Int = 0,
+    var int: Int? = null,
     var str: String? = null,
 ) {
     /** Resets to default values so that the object can be recycled. */
@@ -54,7 +54,7 @@
     }
 
     /** Sets this to store an int change. */
-    fun set(value: Int) {
+    fun set(value: Int?) {
         type = DataType.INT
         int = value
     }
diff --git a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt
index 2c299d6..1712dab 100644
--- a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt
@@ -138,7 +138,7 @@
     }
 
     /** Logs a Int change. */
-    fun logChange(prefix: String, columnName: String, value: Int) {
+    fun logChange(prefix: String, columnName: String, value: Int?) {
         logChange(systemClock.currentTimeMillis(), prefix, columnName, value)
     }
 
@@ -155,7 +155,7 @@
         change.set(value)
     }
 
-    private fun logChange(timestamp: Long, prefix: String, columnName: String, value: Int) {
+    private fun logChange(timestamp: Long, prefix: String, columnName: String, value: Int?) {
         val change = obtain(timestamp, prefix, columnName)
         change.set(value)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
index d555d05..00e9a79 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
@@ -27,6 +27,7 @@
 import android.content.res.ColorStateList;
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffColorFilter;
+import android.graphics.drawable.AnimatedVectorDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.Build;
 import android.util.Log;
@@ -179,6 +180,7 @@
             if (mCurrentActivePosition == position) {
                 mCurrentActivePosition = -1;
             }
+            mStatusIcon.setVisibility(View.GONE);
 
             if (mController.isAnyDeviceTransferring()) {
                 if (device.getState() == MediaDeviceState.STATE_CONNECTING
@@ -233,8 +235,8 @@
                         setUpDeviceIcon(device);
                         mSubTitleText.setText(device.getSubtextString());
                         Drawable deviceStatusIcon =
-                                isActiveWithOngoingSession ? mContext.getDrawable(
-                                        R.drawable.media_output_status_session)
+                                device.hasOngoingSession() ? mContext.getDrawable(
+                                        R.drawable.ic_sound_bars_anim)
                                         : Api34Impl.getDeviceStatusIconBasedOnSelectionBehavior(
                                                 device,
                                                 mContext);
@@ -331,7 +333,19 @@
                     setSingleLineLayout(getItemTitle(device));
                     if (mController.isAdvancedLayoutSupported()
                             && mController.isSubStatusSupported()) {
-                        updateClickActionBasedOnSelectionBehavior(device);
+                        Drawable deviceStatusIcon =
+                                device.hasOngoingSession() ? mContext.getDrawable(
+                                        R.drawable.ic_sound_bars_anim)
+                                        : Api34Impl.getDeviceStatusIconBasedOnSelectionBehavior(
+                                                device,
+                                                mContext);
+                        if (deviceStatusIcon != null) {
+                            updateDeviceStatusIcon(deviceStatusIcon);
+                            mStatusIcon.setVisibility(View.VISIBLE);
+                        }
+                        updateTwoLineLayoutContentAlpha(
+                                updateClickActionBasedOnSelectionBehavior(device)
+                                        ? DEVICE_CONNECTED_ALPHA : DEVICE_DISCONNECTED_ALPHA);
                     } else {
                         updateFullItemClickListener(v -> onItemClick(v, device));
                     }
@@ -386,6 +400,9 @@
         private void updateDeviceStatusIcon(Drawable drawable) {
             mStatusIcon.setImageDrawable(drawable);
             mStatusIcon.setColorFilter(mController.getColorItemContent());
+            if (drawable instanceof AnimatedVectorDrawable) {
+                ((AnimatedVectorDrawable) drawable).start();
+            }
         }
 
         private void updateProgressBarColor() {
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
index 35baf013..12d6b7c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
@@ -20,6 +20,8 @@
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.os.Bundle;
+import android.text.Editable;
+import android.text.TextWatcher;
 import android.text.method.HideReturnsTransformationMethod;
 import android.text.method.PasswordTransformationMethod;
 import android.util.Log;
@@ -64,11 +66,51 @@
     private String mCurrentBroadcastName;
     private String mCurrentBroadcastCode;
     private boolean mIsStopbyUpdateBroadcastCode = false;
+    private TextWatcher mTextWatcher = new TextWatcher() {
+        @Override
+        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+            // Do nothing
+        }
+
+        @Override
+        public void onTextChanged(CharSequence s, int start, int before, int count) {
+            // Do nothing
+        }
+
+        @Override
+        public void afterTextChanged(Editable s) {
+            if (mAlertDialog == null || mBroadcastErrorMessage == null) {
+                return;
+            }
+            boolean breakBroadcastCodeRuleTextLengthLessThanMin =
+                    s.length() > 0 && s.length() < BROADCAST_CODE_MIN_LENGTH;
+            boolean breakBroadcastCodeRuleTextLengthMoreThanMax =
+                    s.length() > BROADCAST_CODE_MAX_LENGTH;
+            boolean breakRule = breakBroadcastCodeRuleTextLengthLessThanMin
+                    || breakBroadcastCodeRuleTextLengthMoreThanMax;
+
+            if (breakBroadcastCodeRuleTextLengthLessThanMin) {
+                mBroadcastErrorMessage.setText(
+                        R.string.media_output_broadcast_code_hint_no_less_than_min);
+            } else if (breakBroadcastCodeRuleTextLengthMoreThanMax) {
+                mBroadcastErrorMessage.setText(
+                        R.string.media_output_broadcast_code_hint_no_more_than_max);
+            }
+
+            mBroadcastErrorMessage.setVisibility(breakRule ? View.VISIBLE : View.INVISIBLE);
+            Button positiveBtn = mAlertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
+            if (positiveBtn != null) {
+                positiveBtn.setEnabled(breakRule ? false : true);
+            }
+        }
+    };
 
     static final int METADATA_BROADCAST_NAME = 0;
     static final int METADATA_BROADCAST_CODE = 1;
 
     private static final int MAX_BROADCAST_INFO_UPDATE = 3;
+    private static final int BROADCAST_CODE_MAX_LENGTH = 16;
+    private static final int BROADCAST_CODE_MIN_LENGTH = 4;
 
     MediaOutputBroadcastDialog(Context context, boolean aboveStatusbar,
             BroadcastSender broadcastSender, MediaOutputController mediaOutputController) {
@@ -219,6 +261,9 @@
                 R.layout.media_output_broadcast_update_dialog, null);
         final EditText editText = layout.requireViewById(R.id.broadcast_edit_text);
         editText.setText(editString);
+        if (isBroadcastCode) {
+            editText.addTextChangedListener(mTextWatcher);
+        }
         mBroadcastErrorMessage = layout.requireViewById(R.id.broadcast_error_message);
         mAlertDialog = new Builder(mContext)
                 .setTitle(isBroadcastCode ? R.string.media_output_broadcast_code
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
index 8000cd8..fab8c06 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
@@ -16,7 +16,9 @@
 
 package com.android.systemui.media.taptotransfer.receiver
 
+import android.animation.TimeInterpolator
 import android.annotation.SuppressLint
+import android.animation.ValueAnimator
 import android.app.StatusBarManager
 import android.content.Context
 import android.graphics.Rect
@@ -31,8 +33,10 @@
 import android.view.WindowManager
 import android.view.accessibility.AccessibilityManager
 import android.view.View.ACCESSIBILITY_LIVE_REGION_ASSERTIVE
+import android.view.View.ACCESSIBILITY_LIVE_REGION_NONE
 import com.android.internal.widget.CachingIconView
 import com.android.systemui.R
+import com.android.systemui.animation.Interpolators
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.ui.binder.TintedIconViewBinder
 import com.android.systemui.dagger.SysUISingleton
@@ -101,6 +105,13 @@
         fitInsetsTypes = 0 // Ignore insets from all system bars
     }
 
+    // Value animator that controls the bouncing animation of views.
+    private val bounceAnimator = ValueAnimator.ofFloat(0f, 1f).apply {
+        repeatCount = ValueAnimator.INFINITE
+        repeatMode = ValueAnimator.REVERSE
+        duration = ICON_BOUNCE_ANIM_DURATION
+    }
+
     private val commandQueueCallbacks = object : CommandQueue.Callbacks {
         override fun updateMediaTapToTransferReceiverDisplay(
             @StatusBarManager.MediaTransferReceiverState displayState: Int,
@@ -203,44 +214,52 @@
 
         val iconView = currentView.getAppIconView()
         iconView.setPadding(iconPadding, iconPadding, iconPadding, iconPadding)
-        iconView.accessibilityLiveRegion = ACCESSIBILITY_LIVE_REGION_ASSERTIVE
         TintedIconViewBinder.bind(iconInfo.toTintedIcon(), iconView)
+
+        val iconContainerView = currentView.getIconContainerView()
+        iconContainerView.accessibilityLiveRegion = ACCESSIBILITY_LIVE_REGION_ASSERTIVE
     }
 
     override fun animateViewIn(view: ViewGroup) {
-        val appIconView = view.getAppIconView()
+        val iconContainerView = view.getIconContainerView()
         val iconRippleView: ReceiverChipRippleView = view.requireViewById(R.id.icon_glow_ripple)
         val rippleView: ReceiverChipRippleView = view.requireViewById(R.id.ripple)
-        animateViewTranslationAndFade(appIconView, -1 * getTranslationAmount(), 1f)
-        animateViewTranslationAndFade(iconRippleView, -1 * getTranslationAmount(), 1f)
+        val translationYBy = getTranslationAmount()
+        // Make the icon container view starts animation from bottom of the screen.
+        iconContainerView.translationY += rippleController.getReceiverIconSize()
+        animateViewTranslationAndFade(
+            iconContainerView,
+            translationYBy = -1 * translationYBy,
+            alphaEndValue = 1f,
+            Interpolators.EMPHASIZED_DECELERATE,
+        ) {
+            animateBouncingView(iconContainerView, translationYBy * BOUNCE_TRANSLATION_RATIO)
+        }
         rippleController.expandToInProgressState(rippleView, iconRippleView)
     }
 
     override fun animateViewOut(view: ViewGroup, removalReason: String?, onAnimationEnd: Runnable) {
-        val appIconView = view.getAppIconView()
-        val iconRippleView: ReceiverChipRippleView = view.requireViewById(R.id.icon_glow_ripple)
+        val iconContainerView = view.getIconContainerView()
         val rippleView: ReceiverChipRippleView = view.requireViewById(R.id.ripple)
+        val translationYBy = getTranslationAmount()
+
+        // Remove update listeners from bounce animator to prevent any conflict with
+        // translation animation.
+        bounceAnimator.removeAllUpdateListeners()
+        bounceAnimator.cancel()
         if (removalReason == ChipStateReceiver.TRANSFER_TO_RECEIVER_SUCCEEDED.name &&
                 mediaTttFlags.isMediaTttReceiverSuccessRippleEnabled()) {
             rippleController.expandToSuccessState(rippleView, onAnimationEnd)
             animateViewTranslationAndFade(
-                iconRippleView,
-                -1 * getTranslationAmount(),
-                0f,
-                translationDuration = ICON_TRANSLATION_SUCCEEDED_DURATION,
-                alphaDuration = ICON_TRANSLATION_SUCCEEDED_DURATION,
-            )
-            animateViewTranslationAndFade(
-                appIconView,
-                -1 * getTranslationAmount(),
+                iconContainerView,
+                -1 * translationYBy,
                 0f,
                 translationDuration = ICON_TRANSLATION_SUCCEEDED_DURATION,
                 alphaDuration = ICON_TRANSLATION_SUCCEEDED_DURATION,
             )
         } else {
             rippleController.collapseRipple(rippleView, onAnimationEnd)
-            animateViewTranslationAndFade(iconRippleView, getTranslationAmount(), 0f)
-            animateViewTranslationAndFade(appIconView, getTranslationAmount(), 0f)
+            animateViewTranslationAndFade(iconContainerView, translationYBy, 0f)
         }
     }
 
@@ -252,15 +271,19 @@
 
     /** Animation of view translation and fading. */
     private fun animateViewTranslationAndFade(
-        view: View,
+        view: ViewGroup,
         translationYBy: Float,
         alphaEndValue: Float,
+        interpolator: TimeInterpolator? = null,
         translationDuration: Long = ICON_TRANSLATION_ANIM_DURATION,
         alphaDuration: Long = ICON_ALPHA_ANIM_DURATION,
+        onAnimationEnd: Runnable? = null,
     ) {
         view.animate()
             .translationYBy(translationYBy)
+            .setInterpolator(interpolator)
             .setDuration(translationDuration)
+            .withEndAction { onAnimationEnd?.run() }
             .start()
         view.animate()
             .alpha(alphaEndValue)
@@ -270,17 +293,42 @@
 
     /** Returns the amount that the chip will be translated by in its intro animation. */
     private fun getTranslationAmount(): Float {
-        return rippleController.getRippleSize() * 0.5f -
-            rippleController.getReceiverIconSize()
+        return rippleController.getRippleSize() * 0.5f
     }
 
     private fun View.getAppIconView(): CachingIconView {
         return this.requireViewById(R.id.app_icon)
     }
 
+    private fun View.getIconContainerView(): ViewGroup {
+        return this.requireViewById(R.id.icon_container_view)
+    }
+
+    private fun animateBouncingView(iconContainerView: ViewGroup, translationYBy: Float) {
+        if (bounceAnimator.isStarted) {
+            return
+        }
+
+        addViewToBounceAnimation(iconContainerView, translationYBy)
+
+        // In order not to announce description every time the view animate.
+        iconContainerView.accessibilityLiveRegion = ACCESSIBILITY_LIVE_REGION_NONE
+        bounceAnimator.start()
+    }
+
+    private fun addViewToBounceAnimation(view: View, translationYBy: Float) {
+        val prevTranslationY = view.translationY
+        bounceAnimator.addUpdateListener { updateListener ->
+            val progress = updateListener.animatedValue as Float
+            view.translationY = prevTranslationY + translationYBy * progress
+        }
+    }
+
     companion object {
         private const val ICON_TRANSLATION_ANIM_DURATION = 500L
+        private const val ICON_BOUNCE_ANIM_DURATION = 750L
         private const val ICON_TRANSLATION_SUCCEEDED_DURATION = 167L
+        private const val BOUNCE_TRANSLATION_RATIO = 0.15f
         private val ICON_ALPHA_ANIM_DURATION = 5.frames
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
index 1678c6e..3088d8b 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
@@ -46,10 +46,10 @@
 import dagger.Subcomponent
 import dagger.multibindings.ClassKey
 import dagger.multibindings.IntoMap
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.SupervisorJob
 import javax.inject.Qualifier
 import javax.inject.Scope
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.SupervisorJob
 
 @Qualifier @Retention(AnnotationRetention.BINARY) annotation class MediaProjectionAppSelector
 
@@ -110,6 +110,12 @@
         @Provides
         @MediaProjectionAppSelector
         @MediaProjectionAppSelectorScope
+        fun provideCallerPackageName(activity: MediaProjectionAppSelectorActivity): String? =
+            activity.callingPackage
+
+        @Provides
+        @MediaProjectionAppSelector
+        @MediaProjectionAppSelectorScope
         fun bindConfigurationController(
             activity: MediaProjectionAppSelectorActivity
         ): ConfigurationController = ConfigurationControllerImpl(activity)
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
index 52c7ca3..219629b 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
@@ -36,16 +36,16 @@
     private val flags: FeatureFlags,
     @HostUserHandle private val hostUserHandle: UserHandle,
     @MediaProjectionAppSelector private val scope: CoroutineScope,
-    @MediaProjectionAppSelector private val appSelectorComponentName: ComponentName
+    @MediaProjectionAppSelector private val appSelectorComponentName: ComponentName,
+    @MediaProjectionAppSelector private val callerPackageName: String?
 ) {
 
     fun init() {
         scope.launch {
             val recentTasks = recentTaskListProvider.loadRecentTasks()
 
-            val tasks = recentTasks
-                .filterDevicePolicyRestrictedTasks()
-                .sortedTasks()
+            val tasks =
+                recentTasks.filterDevicePolicyRestrictedTasks().filterAppSelector().sortedTasks()
 
             view.bind(tasks)
         }
@@ -67,8 +67,13 @@
             filter { UserHandle.of(it.userId) == hostUserHandle }
         }
 
+    private fun List<RecentTask>.filterAppSelector(): List<RecentTask> = filter {
+        // Only take tasks that is not the app selector
+        it.topActivityComponent != appSelectorComponentName
+    }
+
     private fun List<RecentTask>.sortedTasks(): List<RecentTask> = sortedBy {
         // Show normal tasks first and only then tasks with opened app selector
-        it.topActivityComponent == appSelectorComponentName
+        it.topActivityComponent?.packageName == callerPackageName
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
index e0ba543..f409b23 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
@@ -36,7 +36,6 @@
 import androidx.dynamicanimation.animation.DynamicAnimation
 import com.android.internal.util.LatencyTracker
 import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.plugins.MotionEventsHandlerBase
 import com.android.systemui.plugins.NavigationEdgeBackPlugin
 import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.statusbar.policy.ConfigurationController
@@ -592,10 +591,6 @@
         windowManager.addView(mView, layoutParams)
     }
 
-    override fun setMotionEventsHandler(motionEventsHandler: MotionEventsHandlerBase?) {
-        // TODO(255697805): Integrate MotionEventHandler for trackpad.
-    }
-
     private fun isDragAwayFromEdge(velocityPxPerSecThreshold: Int = 0) = velocityTracker!!.run {
         computeCurrentVelocity(PX_PER_SEC)
         val velocity = xVelocity.takeIf { mView.isLeftPanel } ?: (xVelocity * -1)
@@ -1048,4 +1043,4 @@
             else -> startValue
         }.also { previousValue = it }
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 3e6eb05..389034a 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -18,7 +18,6 @@
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION;
 
 import static com.android.systemui.classifier.Classifier.BACK_GESTURE;
-import static com.android.systemui.navigationbar.gestural.Utilities.getTrackpadScale;
 import static com.android.systemui.navigationbar.gestural.Utilities.isTrackpadMotionEvent;
 
 import android.annotation.NonNull;
@@ -272,7 +271,6 @@
     private LogArray mGestureLogOutsideInsets = new LogArray(MAX_NUM_LOGGED_GESTURES);
 
     private final GestureNavigationSettingsObserver mGestureNavigationSettingsObserver;
-    private final MotionEventsHandler mMotionEventsHandler;
 
     private final NavigationEdgeBackPlugin.BackCallback mBackCallback =
             new NavigationEdgeBackPlugin.BackCallback() {
@@ -404,7 +402,6 @@
 
         mGestureNavigationSettingsObserver = new GestureNavigationSettingsObserver(
                 mContext.getMainThreadHandler(), mContext, this::onNavigationSettingsChanged);
-        mMotionEventsHandler = new MotionEventsHandler(featureFlags, getTrackpadScale(context));
 
         updateCurrentUserResources();
     }
@@ -624,7 +621,6 @@
             Trace.beginSection("setEdgeBackPlugin");
             mEdgeBackPlugin = edgeBackPlugin;
             mEdgeBackPlugin.setBackCallback(mBackCallback);
-            mEdgeBackPlugin.setMotionEventsHandler(mMotionEventsHandler);
             mEdgeBackPlugin.setLayoutParams(createLayoutParams());
             updateDisplaySize();
         } finally {
@@ -871,7 +867,7 @@
     }
 
     private void onMotionEvent(MotionEvent ev) {
-        mMotionEventsHandler.onMotionEvent(ev);
+        boolean isTrackpadEvent = isTrackpadMotionEvent(mIsTrackpadGestureBackEnabled, ev);
         int action = ev.getActionMasked();
         if (action == MotionEvent.ACTION_DOWN) {
             if (DEBUG_MISSING_GESTURE) {
@@ -881,7 +877,6 @@
             // Verify if this is in within the touch region and we aren't in immersive mode, and
             // either the bouncer is showing or the notification panel is hidden
             mInputEventReceiver.setBatchingEnabled(false);
-            boolean isTrackpadEvent = isTrackpadMotionEvent(mIsTrackpadGestureBackEnabled, ev);
             if (isTrackpadEvent) {
                 // TODO: show the back arrow based on the direction of the swipe.
                 mIsOnLeftEdge = false;
@@ -921,7 +916,7 @@
             if (!mThresholdCrossed) {
                 mEndPoint.x = (int) ev.getX();
                 mEndPoint.y = (int) ev.getY();
-                if (action == MotionEvent.ACTION_POINTER_DOWN) {
+                if (action == MotionEvent.ACTION_POINTER_DOWN && !isTrackpadEvent) {
                     if (mAllowGesture) {
                         logGesture(SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE_MULTI_TOUCH);
                         if (DEBUG_MISSING_GESTURE) {
@@ -947,8 +942,8 @@
                         mLogGesture = false;
                         return;
                     }
-                    float dx = Math.abs(mMotionEventsHandler.getDisplacementX(ev));
-                    float dy = Math.abs(mMotionEventsHandler.getDisplacementY(ev));
+                    float dx = Math.abs(ev.getX() - mDownPoint.x);
+                    float dy = Math.abs(ev.getY() - mDownPoint.y);
                     if (dy > dx && dy > mTouchSlop) {
                         if (mAllowGesture) {
                             logGesture(SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE_VERTICAL_MOVE);
@@ -1086,7 +1081,6 @@
         pw.println("  mGestureLogInsideInsets=" + String.join("\n", mGestureLogInsideInsets));
         pw.println("  mGestureLogOutsideInsets=" + String.join("\n", mGestureLogOutsideInsets));
         pw.println("  mEdgeBackPlugin=" + mEdgeBackPlugin);
-        pw.println("  mMotionEventsHandler=" + mMotionEventsHandler);
         if (mEdgeBackPlugin != null) {
             mEdgeBackPlugin.dump(pw);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/MotionEventsHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/MotionEventsHandler.java
deleted file mode 100644
index e9b5453..0000000
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/MotionEventsHandler.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.navigationbar.gestural;
-
-import static android.view.MotionEvent.AXIS_GESTURE_X_OFFSET;
-import static android.view.MotionEvent.AXIS_GESTURE_Y_OFFSET;
-
-import static com.android.systemui.navigationbar.gestural.Utilities.isTrackpadMotionEvent;
-
-import android.graphics.PointF;
-import android.view.MotionEvent;
-
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
-import com.android.systemui.plugins.MotionEventsHandlerBase;
-
-/** Handles both trackpad and touch events and report displacements in both axis's. */
-public class MotionEventsHandler implements MotionEventsHandlerBase {
-
-    private final boolean mIsTrackpadGestureBackEnabled;
-    private final int mScale;
-
-    private final PointF mDownPos = new PointF();
-    private final PointF mLastPos = new PointF();
-    private float mCurrentTrackpadOffsetX = 0;
-    private float mCurrentTrackpadOffsetY = 0;
-
-    public MotionEventsHandler(FeatureFlags featureFlags, int scale) {
-        mIsTrackpadGestureBackEnabled = featureFlags.isEnabled(Flags.TRACKPAD_GESTURE_BACK);
-        mScale = scale;
-    }
-
-    @Override
-    public void onMotionEvent(MotionEvent ev) {
-        switch (ev.getActionMasked()) {
-            case MotionEvent.ACTION_DOWN:
-                onActionDown(ev);
-                break;
-            case MotionEvent.ACTION_MOVE:
-                onActionMove(ev);
-                break;
-            case MotionEvent.ACTION_UP:
-            case MotionEvent.ACTION_CANCEL:
-                onActionUp(ev);
-                break;
-            default:
-                break;
-        }
-    }
-
-    private void onActionDown(MotionEvent ev) {
-        reset();
-        if (!isTrackpadMotionEvent(mIsTrackpadGestureBackEnabled, ev)) {
-            mDownPos.set(ev.getX(), ev.getY());
-            mLastPos.set(mDownPos);
-        }
-    }
-
-    private void onActionMove(MotionEvent ev) {
-        updateMovements(ev);
-    }
-
-    private void onActionUp(MotionEvent ev) {
-        updateMovements(ev);
-    }
-
-    private void updateMovements(MotionEvent ev) {
-        if (isTrackpadMotionEvent(mIsTrackpadGestureBackEnabled, ev)) {
-            mCurrentTrackpadOffsetX += ev.getAxisValue(AXIS_GESTURE_X_OFFSET) * mScale;
-            mCurrentTrackpadOffsetY += ev.getAxisValue(AXIS_GESTURE_Y_OFFSET) * mScale;
-        } else {
-            mLastPos.set(ev.getX(), ev.getY());
-        }
-    }
-
-    private void reset() {
-        mDownPos.set(0, 0);
-        mLastPos.set(0, 0);
-        mCurrentTrackpadOffsetX = 0;
-        mCurrentTrackpadOffsetY = 0;
-    }
-
-    @Override
-    public float getDisplacementX(MotionEvent ev) {
-        return isTrackpadMotionEvent(mIsTrackpadGestureBackEnabled, ev) ? mCurrentTrackpadOffsetX
-                : mLastPos.x - mDownPos.x;
-    }
-
-    @Override
-    public float getDisplacementY(MotionEvent ev) {
-        return isTrackpadMotionEvent(mIsTrackpadGestureBackEnabled, ev) ? mCurrentTrackpadOffsetY
-                : mLastPos.y - mDownPos.y;
-    }
-
-    @Override
-    public String dump() {
-        return "mDownPos: " + mDownPos + ", mLastPos: " + mLastPos + ", mCurrentTrackpadOffsetX: "
-                + mCurrentTrackpadOffsetX + ", mCurrentTrackpadOffsetY: " + mCurrentTrackpadOffsetY;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
index de0f9b2..590efbb 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
@@ -53,7 +53,6 @@
 import com.android.systemui.R;
 import com.android.systemui.animation.Interpolators;
 import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.plugins.MotionEventsHandlerBase;
 import com.android.systemui.plugins.NavigationEdgeBackPlugin;
 import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.shared.navigationbar.RegionSamplingHelper;
@@ -200,6 +199,8 @@
      */
     private boolean mIsLeftPanel;
 
+    private float mStartX;
+    private float mStartY;
     private float mCurrentAngle;
     /**
      * The current translation of the arrow
@@ -230,8 +231,6 @@
     private final Handler mHandler = new Handler();
     private final Runnable mFailsafeRunnable = this::onFailsafe;
 
-    private MotionEventsHandlerBase mMotionEventsHandler;
-
     private DynamicAnimation.OnAnimationEndListener mSetGoneEndListener
             = new DynamicAnimation.OnAnimationEndListener() {
         @Override
@@ -438,11 +437,6 @@
         mWindowManager.addView(this, mLayoutParams);
     }
 
-    @Override
-    public void setMotionEventsHandler(MotionEventsHandlerBase motionEventsHandler) {
-        mMotionEventsHandler = motionEventsHandler;
-    }
-
     /**
      * Adjusts the sampling rect to conform to the actual visible bounding box of the arrow.
      */
@@ -487,6 +481,8 @@
             case MotionEvent.ACTION_DOWN:
                 mDragSlopPassed = false;
                 resetOnDown();
+                mStartX = event.getX();
+                mStartY = event.getY();
                 setVisibility(VISIBLE);
                 updatePosition(event.getY());
                 mRegionSamplingHelper.start(mSamplingRect);
@@ -730,9 +726,10 @@
     }
 
     private void handleMoveEvent(MotionEvent event) {
-        float xOffset = mMotionEventsHandler.getDisplacementX(event);
-        float touchTranslation = MathUtils.abs(xOffset);
-        float yOffset = mMotionEventsHandler.getDisplacementY(event);
+        float x = event.getX();
+        float y = event.getY();
+        float touchTranslation = MathUtils.abs(x - mStartX);
+        float yOffset = y - mStartY;
         float delta = touchTranslation - mPreviousTouchTranslation;
         if (Math.abs(delta) > 0) {
             if (Math.signum(delta) == Math.signum(mTotalTouchDelta)) {
@@ -793,14 +790,16 @@
         }
 
         // Last if the direction in Y is bigger than X * 2 we also abort
-        if (Math.abs(yOffset) > Math.abs(xOffset) * 2) {
+        if (Math.abs(yOffset) > Math.abs(x - mStartX) * 2) {
             triggerBack = false;
         }
         if (DEBUG_MISSING_GESTURE && mTriggerBack != triggerBack) {
             Log.d(DEBUG_MISSING_GESTURE_TAG, "set mTriggerBack=" + triggerBack
                     + ", mTotalTouchDelta=" + mTotalTouchDelta
                     + ", mMinDeltaForSwitch=" + mMinDeltaForSwitch
-                    + ", yOffset=" + yOffset + mMotionEventsHandler.dump());
+                    + ", yOffset=" + yOffset
+                    + ", x=" + x
+                    + ", mStartX=" + mStartX);
         }
         setTriggerBack(triggerBack, true /* animated */);
 
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/Utilities.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/Utilities.java
index 30d2d7a..1345c9b 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/Utilities.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/Utilities.java
@@ -18,21 +18,13 @@
 
 import static android.view.MotionEvent.CLASSIFICATION_MULTI_FINGER_SWIPE;
 
-import android.content.Context;
 import android.view.MotionEvent;
-import android.view.ViewConfiguration;
 
 public final class Utilities {
 
-    private static final int TRACKPAD_GESTURE_SCALE = 200;
-
     public static boolean isTrackpadMotionEvent(boolean isTrackpadGestureBackEnabled,
             MotionEvent event) {
         return isTrackpadGestureBackEnabled
                 && event.getClassification() == CLASSIFICATION_MULTI_FINGER_SWIPE;
     }
-
-    public static int getTrackpadScale(Context context) {
-        return ViewConfiguration.get(context).getScaledTouchSlop() * TRACKPAD_GESTURE_SCALE;
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/process/ProcessWrapper.java b/packages/SystemUI/src/com/android/systemui/process/ProcessWrapper.java
index 7db293d..245cf89 100644
--- a/packages/SystemUI/src/com/android/systemui/process/ProcessWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/process/ProcessWrapper.java
@@ -16,11 +16,16 @@
 
 package com.android.systemui.process;
 
+import javax.inject.Inject;
+
 /**
  * A simple wrapper that provides access to process-related details. This facilitates testing by
  * providing a mockable target around these details.
  */
 public class ProcessWrapper {
+    @Inject
+    public ProcessWrapper() {}
+
     public int getUserHandleIdentifier() {
         return android.os.Process.myUserHandle().getIdentifier();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
index 628964a..25a5c61 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
@@ -22,6 +22,7 @@
 import android.hardware.display.NightDisplayListener;
 import android.os.Handler;
 
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.media.dagger.MediaModule;
 import com.android.systemui.qs.AutoAddTracker;
@@ -53,6 +54,7 @@
 public interface QSModule {
 
     @Provides
+    @SysUISingleton
     static AutoTileManager provideAutoTileManager(
             Context context,
             AutoAddTracker.Builder autoAddTrackerBuilder,
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index f53f824..9286d29 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -2144,6 +2144,7 @@
     void flingToHeight(float vel, boolean expand, float target,
             float collapseSpeedUpFactor, boolean expandBecauseOfFalsing) {
         mLastFlingWasExpanding = expand;
+        mShadeLog.logLastFlingWasExpanding(expand);
         mHeadsUpTouchHelper.notifyFling(!expand);
         mKeyguardStateController.notifyPanelFlingStart(!expand /* flingingToDismiss */);
         setClosingWithAlphaFadeout(!expand && !isOnKeyguard() && getFadeoutAlpha() == 1.0f);
@@ -4624,6 +4625,7 @@
         ipw.println(mBlockingExpansionForCurrentTouch);
         ipw.print("mExpectingSynthesizedDown="); ipw.println(mExpectingSynthesizedDown);
         ipw.print("mLastEventSynthesizedDown="); ipw.println(mLastEventSynthesizedDown);
+        ipw.print("mLastFlingWasExpanding="); ipw.println(mLastFlingWasExpanding);
         ipw.print("mInterpolatedDarkAmount="); ipw.println(mInterpolatedDarkAmount);
         ipw.print("mLinearDarkAmount="); ipw.println(mLinearDarkAmount);
         ipw.print("mPulsing="); ipw.println(mPulsing);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
index 11617be..26c839de 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
@@ -20,7 +20,6 @@
 import com.android.systemui.log.dagger.ShadeLog
 import com.android.systemui.plugins.log.LogBuffer
 import com.android.systemui.plugins.log.LogLevel
-import com.android.systemui.plugins.log.LogMessage
 import com.google.errorprone.annotations.CompileTimeConstant
 import javax.inject.Inject
 
@@ -234,4 +233,19 @@
             }
         )
     }
+
+    fun logLastFlingWasExpanding(
+            expand: Boolean
+    ) {
+        buffer.log(
+                TAG,
+                LogLevel.VERBOSE,
+                {
+                    bool1 = expand
+                },
+                {
+                    "NPVC mLastFlingWasExpanding set to: $bool1"
+                }
+        )
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 8d68bce..250900e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -25,6 +25,7 @@
 import static android.view.View.GONE;
 import static android.view.View.VISIBLE;
 
+import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_AVAILABLE;
 import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED;
 import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED;
 import static com.android.keyguard.KeyguardUpdateMonitor.getCurrentUser;
@@ -96,6 +97,7 @@
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
 import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.plugins.log.LogLevel;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.KeyguardIndicationTextView;
@@ -128,7 +130,7 @@
 @SysUISingleton
 public class KeyguardIndicationController {
 
-    private static final String TAG = "KeyguardIndication";
+    public static final String TAG = "KeyguardIndication";
     private static final boolean DEBUG_CHARGING_SPEED = false;
 
     private static final int MSG_SHOW_ACTION_TO_UNLOCK = 1;
@@ -328,9 +330,11 @@
         mInitialTextColorState = mTopIndicationView != null
                 ? mTopIndicationView.getTextColors() : ColorStateList.valueOf(Color.WHITE);
         mRotateTextViewController = new KeyguardIndicationRotateTextViewController(
-            mLockScreenIndicationView,
-            mExecutor,
-            mStatusBarStateController);
+                mLockScreenIndicationView,
+                mExecutor,
+                mStatusBarStateController,
+                mKeyguardLogger
+        );
         updateDeviceEntryIndication(false /* animate */);
         updateOrganizedOwnedDevice();
         if (mBroadcastReceiver == null) {
@@ -842,6 +846,7 @@
      * may continuously be cycled through.
      */
     protected final void updateDeviceEntryIndication(boolean animate) {
+        mKeyguardLogger.logUpdateDeviceEntryIndication(animate, mVisible, mDozing);
         if (!mVisible) {
             return;
         }
@@ -1092,18 +1097,23 @@
                 }
             }
 
+            final boolean faceAuthUnavailable = biometricSourceType == FACE
+                    && msgId == BIOMETRIC_HELP_FACE_NOT_AVAILABLE;
+
             // TODO(b/141025588): refactor to reduce repetition of code/comments
             // Only checking if unlocking with Biometric is allowed (no matter strong or non-strong
             // as long as primary auth, i.e. PIN/pattern/password, is not required), so it's ok to
             // pass true for isStrongBiometric to isUnlockingWithBiometricAllowed() to bypass the
             // check of whether non-strong biometric is allowed
             if (!mKeyguardUpdateMonitor
-                    .isUnlockingWithBiometricAllowed(true /* isStrongBiometric */)) {
+                    .isUnlockingWithBiometricAllowed(true /* isStrongBiometric */)
+                    && !faceAuthUnavailable) {
                 return;
             }
 
             final boolean faceAuthSoftError = biometricSourceType == FACE
-                    && msgId != BIOMETRIC_HELP_FACE_NOT_RECOGNIZED;
+                    && msgId != BIOMETRIC_HELP_FACE_NOT_RECOGNIZED
+                    && msgId != BIOMETRIC_HELP_FACE_NOT_AVAILABLE;
             final boolean faceAuthFailed = biometricSourceType == FACE
                     && msgId == BIOMETRIC_HELP_FACE_NOT_RECOGNIZED; // ran through matcher & failed
             final boolean fpAuthFailed = biometricSourceType == FINGERPRINT
@@ -1146,6 +1156,13 @@
                             getTrustGrantedIndication(),
                             mContext.getString(R.string.keyguard_unlock)
                     );
+                } else if (faceAuthUnavailable) {
+                    showBiometricMessage(
+                            helpString,
+                            isUnlockWithFingerprintPossible
+                                    ? mContext.getString(R.string.keyguard_suggest_fingerprint)
+                                    : mContext.getString(R.string.keyguard_unlock)
+                    );
                 } else {
                     showBiometricMessage(helpString);
                 }
@@ -1429,6 +1446,7 @@
         public void onKeyguardShowingChanged() {
             // All transient messages are gone the next time keyguard is shown
             if (!mKeyguardStateController.isShowing()) {
+                mKeyguardLogger.log(TAG, LogLevel.DEBUG, "clear messages");
                 mTopIndicationView.clearMessages();
                 mRotateTextViewController.clearMessages();
             } else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerExt.kt b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerExt.kt
new file mode 100644
index 0000000..6148b40
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerExt.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar
+
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+/** Returns a [Flow] that emits whenever [StatusBarStateController.isExpanded] changes value. */
+val StatusBarStateController.expansionChanges: Flow<Boolean>
+    get() = conflatedCallbackFlow {
+        val listener =
+            object : StatusBarStateController.StateListener {
+                override fun onExpandedChanged(isExpanded: Boolean) {
+                    trySend(isExpanded)
+                }
+            }
+        trySend(isExpanded)
+        addCallback(listener)
+        awaitClose { removeCallback(listener) }
+    }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java
index 058042c..0c95eab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java
@@ -71,11 +71,9 @@
      * marking them as relevant for setup are allowed to show when device is unprovisioned
      */
     private boolean showNotificationEvenIfUnprovisioned(StatusBarNotification sbn) {
-        final boolean hasPermission = checkUidPermission(
-                Manifest.permission.NOTIFICATION_DURING_SETUP,
-                sbn.getUid()) == PackageManager.PERMISSION_GRANTED;
-        return hasPermission
-                && sbn.getNotification().extras.getBoolean(Notification.EXTRA_ALLOW_DURING_SETUP);
+        // system_server checks the permission so systemui can just check whether the
+        // extra exists
+        return sbn.getNotification().extras.getBoolean(Notification.EXTRA_ALLOW_DURING_SETUP);
     }
 
     private int checkUidPermission(String permission, int uid) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
index e996b78..6bf7668 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
@@ -16,16 +16,15 @@
 
 package com.android.systemui.statusbar.notification.collection.coordinator
 
-import android.database.ContentObserver
 import android.os.UserHandle
 import android.provider.Settings
 import androidx.annotation.VisibleForTesting
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.expansionChanges
 import com.android.systemui.statusbar.notification.NotifPipelineFlags
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
@@ -35,15 +34,14 @@
 import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider
 import com.android.systemui.statusbar.notification.collection.provider.SeenNotificationsProviderImpl
 import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider
+import com.android.systemui.statusbar.policy.HeadsUpManager
+import com.android.systemui.statusbar.policy.headsUpEvents
 import com.android.systemui.util.settings.SecureSettings
-import com.android.systemui.util.settings.SettingsProxy
+import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
 import javax.inject.Inject
-import kotlin.time.Duration.Companion.seconds
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.conflate
 import kotlinx.coroutines.flow.flowOn
@@ -60,6 +58,7 @@
 @Inject
 constructor(
     @Background private val bgDispatcher: CoroutineDispatcher,
+    private val headsUpManager: HeadsUpManager,
     private val keyguardNotificationVisibilityProvider: KeyguardNotificationVisibilityProvider,
     private val keyguardRepository: KeyguardRepository,
     private val notifPipelineFlags: NotifPipelineFlags,
@@ -87,28 +86,53 @@
     private fun attachUnseenFilter(pipeline: NotifPipeline) {
         pipeline.addFinalizeFilter(unseenNotifFilter)
         pipeline.addCollectionListener(collectionListener)
-        scope.launch { clearUnseenWhenKeyguardIsDismissed() }
+        scope.launch { trackUnseenNotificationsWhileUnlocked() }
         scope.launch { invalidateWhenUnseenSettingChanges() }
     }
 
-    private suspend fun clearUnseenWhenKeyguardIsDismissed() {
-        // Use collectLatest so that the suspending block is cancelled if isKeyguardShowing changes
-        // during the timeout period
+    private suspend fun trackUnseenNotificationsWhileUnlocked() {
+        // Use collectLatest so that trackUnseenNotifications() is cancelled when the keyguard is
+        // showing again
         keyguardRepository.isKeyguardShowing.collectLatest { isKeyguardShowing ->
             if (!isKeyguardShowing) {
                 unseenNotifFilter.invalidateList("keyguard no longer showing")
-                delay(SEEN_TIMEOUT)
+                trackUnseenNotifications()
+            }
+        }
+    }
+
+    private suspend fun trackUnseenNotifications() {
+        coroutineScope {
+            launch { clearUnseenNotificationsWhenShadeIsExpanded() }
+            launch { markHeadsUpNotificationsAsSeen() }
+        }
+    }
+
+    private suspend fun clearUnseenNotificationsWhenShadeIsExpanded() {
+        statusBarStateController.expansionChanges.collect { isExpanded ->
+            if (isExpanded) {
                 unseenNotifications.clear()
             }
         }
     }
 
+    private suspend fun markHeadsUpNotificationsAsSeen() {
+        headsUpManager.allEntries
+            .filter { it.isRowPinned }
+            .forEach { unseenNotifications.remove(it) }
+        headsUpManager.headsUpEvents.collect { (entry, isHun) ->
+            if (isHun) {
+                unseenNotifications.remove(entry)
+            }
+        }
+    }
+
     private suspend fun invalidateWhenUnseenSettingChanges() {
         secureSettings
             // emit whenever the setting has changed
-            .settingChangesForUser(
-                Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
+            .observerFlow(
                 UserHandle.USER_ALL,
+                Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
             )
             // perform a query immediately
             .onStart { emit(Unit) }
@@ -136,13 +160,17 @@
     private val collectionListener =
         object : NotifCollectionListener {
             override fun onEntryAdded(entry: NotificationEntry) {
-                if (keyguardRepository.isKeyguardShowing()) {
+                if (
+                    keyguardRepository.isKeyguardShowing() || !statusBarStateController.isExpanded
+                ) {
                     unseenNotifications.add(entry)
                 }
             }
 
             override fun onEntryUpdated(entry: NotificationEntry) {
-                if (keyguardRepository.isKeyguardShowing()) {
+                if (
+                    keyguardRepository.isKeyguardShowing() || !statusBarStateController.isExpanded
+                ) {
                     unseenNotifications.add(entry)
                 }
             }
@@ -212,18 +240,5 @@
 
     companion object {
         private const val TAG = "KeyguardCoordinator"
-        private val SEEN_TIMEOUT = 5.seconds
     }
 }
-
-private fun SettingsProxy.settingChangesForUser(name: String, userHandle: Int): Flow<Unit> =
-    conflatedCallbackFlow {
-        val observer =
-            object : ContentObserver(null) {
-                override fun onChange(selfChange: Boolean) {
-                    trySend(Unit)
-                }
-            }
-        registerContentObserverForUser(name, observer, userHandle)
-        awaitClose { unregisterContentObserver(observer) }
-    }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index c534860..39e4000 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -28,8 +28,11 @@
 import android.content.ContextWrapper;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.content.res.Resources;
 import android.os.AsyncTask;
+import android.os.Build;
 import android.os.CancellationSignal;
+import android.os.Trace;
 import android.os.UserHandle;
 import android.service.notification.StatusBarNotification;
 import android.util.Log;
@@ -38,6 +41,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.widget.ImageMessageConsumer;
+import com.android.systemui.R;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.media.controls.util.MediaFeatureFlag;
@@ -468,6 +472,7 @@
                             result.packageContext,
                             parentLayout,
                             remoteViewClickHandler);
+                    validateView(v, entry, row.getResources());
                     v.setIsRootNamespace(true);
                     applyCallback.setResultView(v);
                 } else {
@@ -475,6 +480,7 @@
                             result.packageContext,
                             existingView,
                             remoteViewClickHandler);
+                    validateView(existingView, entry, row.getResources());
                     existingWrapper.onReinflated();
                 }
             } catch (Exception e) {
@@ -496,6 +502,13 @@
 
             @Override
             public void onViewApplied(View v) {
+                String invalidReason = isValidView(v, entry, row.getResources());
+                if (invalidReason != null) {
+                    handleInflationError(runningInflations, new InflationException(invalidReason),
+                            row.getEntry(), callback);
+                    runningInflations.remove(inflationId);
+                    return;
+                }
                 if (isNewView) {
                     v.setIsRootNamespace(true);
                     applyCallback.setResultView(v);
@@ -553,6 +566,65 @@
         runningInflations.put(inflationId, cancellationSignal);
     }
 
+    /**
+     * Checks if the given View is a valid notification View.
+     *
+     * @return null == valid, non-null == invalid, String represents reason for rejection.
+     */
+    @VisibleForTesting
+    @Nullable
+    static String isValidView(View view,
+            NotificationEntry entry,
+            Resources resources) {
+        if (!satisfiesMinHeightRequirement(view, entry, resources)) {
+            return "inflated notification does not meet minimum height requirement";
+        }
+        return null;
+    }
+
+    private static boolean satisfiesMinHeightRequirement(View view,
+            NotificationEntry entry,
+            Resources resources) {
+        if (!requiresHeightCheck(entry)) {
+            return true;
+        }
+        Trace.beginSection("NotificationContentInflater#satisfiesMinHeightRequirement");
+        int heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
+        int referenceWidth = resources.getDimensionPixelSize(
+                R.dimen.notification_validation_reference_width);
+        int widthSpec = View.MeasureSpec.makeMeasureSpec(referenceWidth, View.MeasureSpec.EXACTLY);
+        view.measure(widthSpec, heightSpec);
+        int minHeight = resources.getDimensionPixelSize(
+                R.dimen.notification_validation_minimum_allowed_height);
+        boolean result = view.getMeasuredHeight() >= minHeight;
+        Trace.endSection();
+        return result;
+    }
+
+    private static boolean requiresHeightCheck(NotificationEntry entry) {
+        // Undecorated custom views are disallowed from S onwards
+        if (entry.targetSdk >= Build.VERSION_CODES.S) {
+            return false;
+        }
+        // No need to check if the app isn't using any custom views
+        Notification notification = entry.getSbn().getNotification();
+        if (notification.contentView == null
+                && notification.bigContentView == null
+                && notification.headsUpContentView == null) {
+            return false;
+        }
+        return true;
+    }
+
+    private static void validateView(View view,
+            NotificationEntry entry,
+            Resources resources) throws InflationException {
+        String invalidReason = isValidView(view, entry, resources);
+        if (invalidReason != null) {
+            throw new InflationException(invalidReason);
+        }
+    }
+
     private static void handleInflationError(
             HashMap<Integer, CancellationSignal> runningInflations, Exception e,
             NotificationEntry notification, @Nullable InflationCallback callback) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
index 8b1a02b..576df7a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
@@ -29,6 +29,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.R;
 import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.qs.AutoAddTracker;
 import com.android.systemui.qs.QSTileHost;
 import com.android.systemui.qs.ReduceBrightColorsController;
@@ -47,6 +48,7 @@
 import com.android.systemui.util.settings.SecureSettings;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Objects;
 
 import javax.inject.Named;
@@ -165,9 +167,10 @@
         if (!mAutoTracker.isAdded(BRIGHTNESS) && mIsReduceBrightColorsAvailable) {
             mReduceBrightColorsController.addCallback(mReduceBrightColorsCallback);
         }
-        if (!mAutoTracker.isAdded(DEVICE_CONTROLS)) {
-            mDeviceControlsController.setCallback(mDeviceControlsCallback);
-        }
+        // We always want this callback, because if the feature stops being supported,
+        // we want to remove the tile from AutoAddTracker. That way it will be re-added when the
+        // feature is reenabled (similar to work tile).
+        mDeviceControlsController.setCallback(mDeviceControlsCallback);
         if (!mAutoTracker.isAdded(WALLET)) {
             initWalletController();
         }
@@ -323,14 +326,30 @@
         @Override
         public void onControlsUpdate(@Nullable Integer position) {
             if (mAutoTracker.isAdded(DEVICE_CONTROLS)) return;
-            if (position != null) {
+            if (position != null && !hasTile(DEVICE_CONTROLS)) {
                 mHost.addTile(DEVICE_CONTROLS, position);
+                mAutoTracker.setTileAdded(DEVICE_CONTROLS);
             }
-            mAutoTracker.setTileAdded(DEVICE_CONTROLS);
             mHandler.post(() -> mDeviceControlsController.removeCallback());
         }
+
+        @Override
+        public void removeControlsAutoTracker() {
+            mAutoTracker.setTileRemoved(DEVICE_CONTROLS);
+        }
     };
 
+    private boolean hasTile(String tileSpec) {
+        if (tileSpec == null) return false;
+        Collection<QSTile> tiles = mHost.getTiles();
+        for (QSTile tile : tiles) {
+            if (tileSpec.equals(tile.getTileSpec())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     private void initWalletController() {
         if (mAutoTracker.isAdded(WALLET)) return;
         Integer position = mWalletController.getWalletPosition();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java
index d24469e..b1553b0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java
@@ -165,6 +165,13 @@
         }
     }
 
+    /**
+     * Get the message that should be shown after the previous text animates out.
+     */
+    public CharSequence getMessage() {
+        return mMessage;
+    }
+
     private AnimatorSet getOutAnimator() {
         AnimatorSet animatorSet = new AnimatorSet();
         Animator fadeOut = ObjectAnimator.ofFloat(this, View.ALPHA, 0f);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
index 416bc71..0727c5a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
@@ -163,7 +163,7 @@
         for (int i = currentSlots.size() - 1; i >= 0; i--) {
             Slot s = currentSlots.get(i);
             slotsToReAdd.put(s, s.getHolderList());
-            removeAllIconsForSlot(s.getName());
+            removeAllIconsForSlot(s.getName(), /* fromNewPipeline */ false);
         }
 
         // Add them all back
@@ -285,7 +285,7 @@
         // Because of the way we cache the icon holders, we need to remove everything any time
         // we get a new set of subscriptions. This might change in the future, but is required
         // to support demo mode for now
-        removeAllIconsForSlot(slotName);
+        removeAllIconsForSlot(slotName, /* fromNewPipeline */ true);
 
         Collections.reverse(subIds);
 
@@ -428,6 +428,14 @@
     /** */
     @Override
     public void removeIcon(String slot, int tag) {
+        // If the new pipeline is on for this icon, don't allow removal, since the new pipeline
+        // will never call this method
+        if (mStatusBarPipelineFlags.isIconControlledByFlags(slot)) {
+            Log.i(TAG, "Ignoring removal of (" + slot + "). "
+                    + "It should be controlled elsewhere");
+            return;
+        }
+
         if (mStatusBarIconList.getIconHolder(slot, tag) == null) {
             return;
         }
@@ -444,6 +452,18 @@
     /** */
     @Override
     public void removeAllIconsForSlot(String slotName) {
+        removeAllIconsForSlot(slotName, /* fromNewPipeline */ false);
+    }
+
+    private void removeAllIconsForSlot(String slotName, boolean fromNewPipeline) {
+        // If the new pipeline is on for this icon, don't allow removal, since the new pipeline
+        // will never call this method
+        if (!fromNewPipeline && mStatusBarPipelineFlags.isIconControlledByFlags(slotName)) {
+            Log.i(TAG, "Ignoring removal of (" + slotName + "). "
+                    + "It should be controlled elsewhere");
+            return;
+        }
+
         Slot slot = mStatusBarIconList.getSlot(slotName);
         if (!slot.hasIconsInSlot()) {
             return;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index c2ca7c6..b115233 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -753,7 +753,7 @@
             mKeyguardMessageAreaController.setMessage("");
         }
         mBypassController.setAltBouncerShowing(isShowingAlternateBouncer);
-        mKeyguardUpdateManager.setUdfpsBouncerShowing(isShowingAlternateBouncer);
+        mKeyguardUpdateManager.setAlternateBouncerShowing(isShowingAlternateBouncer);
 
         if (updateScrim) {
             mCentralSurfaces.updateScrimController();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt
index 15fed32..4a684d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.pipeline
 
+import android.content.Context
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
@@ -23,7 +24,15 @@
 
 /** All flagging methods related to the new status bar pipeline (see b/238425913). */
 @SysUISingleton
-class StatusBarPipelineFlags @Inject constructor(private val featureFlags: FeatureFlags) {
+class StatusBarPipelineFlags
+@Inject
+constructor(
+    context: Context,
+    private val featureFlags: FeatureFlags,
+) {
+    private val mobileSlot = context.getString(com.android.internal.R.string.status_bar_mobile)
+    private val wifiSlot = context.getString(com.android.internal.R.string.status_bar_wifi)
+
     /** True if we should display the mobile icons using the new status bar data pipeline. */
     fun useNewMobileIcons(): Boolean = featureFlags.isEnabled(Flags.NEW_STATUS_BAR_MOBILE_ICONS)
 
@@ -54,4 +63,13 @@
      */
     fun useDebugColoring(): Boolean =
         featureFlags.isEnabled(Flags.NEW_STATUS_BAR_ICONS_DEBUG_COLORING)
+
+    /**
+     * For convenience in the StatusBarIconController, we want to gate some actions based on slot
+     * name and the flag together.
+     *
+     * @return true if this icon is controlled by any of the status bar pipeline flags
+     */
+    fun isIconControlledByFlags(slotName: String): Boolean =
+        slotName == wifiSlot && useNewWifiIcon() || slotName == mobileSlot && useNewMobileIcons()
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/MobileSummaryLog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/MobileSummaryLog.kt
new file mode 100644
index 0000000..2ac9ab3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/MobileSummaryLog.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.dagger
+
+import javax.inject.Qualifier
+
+/**
+ * Logs for mobile data that's **the same across all connections**.
+ *
+ * This buffer should only be used for the mobile parent classes like [MobileConnectionsRepository]
+ * and [MobileIconsInteractor]. It should *not* be used for classes that represent an individual
+ * connection, like [MobileConnectionRepository] or [MobileIconInteractor].
+ */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class MobileSummaryLog
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
index 5f3b0dc..60de1a3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
@@ -118,5 +118,12 @@
         fun provideAirplaneTableLogBuffer(factory: TableLogBufferFactory): TableLogBuffer {
             return factory.create("AirplaneTableLog", 30)
         }
+
+        @Provides
+        @SysUISingleton
+        @MobileSummaryLog
+        fun provideMobileSummaryLogBuffer(factory: TableLogBufferFactory): TableLogBuffer {
+            return factory.create("MobileSummaryLog", 100)
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectivityModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectivityModel.kt
index e618905..97a537a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectivityModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectivityModel.kt
@@ -17,6 +17,8 @@
 package com.android.systemui.statusbar.pipeline.mobile.data.model
 
 import android.net.NetworkCapabilities
+import com.android.systemui.log.table.Diffable
+import com.android.systemui.log.table.TableRowLogger
 
 /** Provides information about a mobile network connection */
 data class MobileConnectivityModel(
@@ -24,4 +26,24 @@
     val isConnected: Boolean = false,
     /** Whether the mobile transport is validated [NetworkCapabilities.NET_CAPABILITY_VALIDATED] */
     val isValidated: Boolean = false,
-)
+) : Diffable<MobileConnectivityModel> {
+    // TODO(b/267767715): Can we implement [logDiffs] and [logFull] generically for data classes?
+    override fun logDiffs(prevVal: MobileConnectivityModel, row: TableRowLogger) {
+        if (prevVal.isConnected != isConnected) {
+            row.logChange(COL_IS_CONNECTED, isConnected)
+        }
+        if (prevVal.isValidated != isValidated) {
+            row.logChange(COL_IS_VALIDATED, isValidated)
+        }
+    }
+
+    override fun logFull(row: TableRowLogger) {
+        row.logChange(COL_IS_CONNECTED, isConnected)
+        row.logChange(COL_IS_VALIDATED, isValidated)
+    }
+
+    companion object {
+        private const val COL_IS_CONNECTED = "isConnected"
+        private const val COL_IS_VALIDATED = "isValidated"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/NetworkNameModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/NetworkNameModel.kt
index c50d82a..78231e2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/NetworkNameModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/NetworkNameModel.kt
@@ -48,15 +48,31 @@
      * This name has been derived from telephony intents. see
      * [android.telephony.TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED]
      */
-    data class Derived(override val name: String) : NetworkNameModel {
+    data class IntentDerived(override val name: String) : NetworkNameModel {
         override fun logDiffs(prevVal: NetworkNameModel, row: TableRowLogger) {
-            if (prevVal !is Derived || prevVal.name != name) {
-                row.logChange(COL_NETWORK_NAME, "Derived($name)")
+            if (prevVal !is IntentDerived || prevVal.name != name) {
+                row.logChange(COL_NETWORK_NAME, "IntentDerived($name)")
             }
         }
 
         override fun logFull(row: TableRowLogger) {
-            row.logChange(COL_NETWORK_NAME, "Derived($name)")
+            row.logChange(COL_NETWORK_NAME, "IntentDerived($name)")
+        }
+    }
+
+    /**
+     * This name has been derived from the sim via
+     * [android.telephony.TelephonyManager.getSimOperatorName].
+     */
+    data class SimDerived(override val name: String) : NetworkNameModel {
+        override fun logDiffs(prevVal: NetworkNameModel, row: TableRowLogger) {
+            if (prevVal !is SimDerived || prevVal.name != name) {
+                row.logChange(COL_NETWORK_NAME, "SimDerived($name)")
+            }
+        }
+
+        override fun logFull(row: TableRowLogger) {
+            row.logChange(COL_NETWORK_NAME, "SimDerived($name)")
         }
     }
 
@@ -84,5 +100,5 @@
         str.append(spn)
     }
 
-    return if (str.isNotEmpty()) NetworkNameModel.Derived(str.toString()) else null
+    return if (str.isNotEmpty()) NetworkNameModel.IntentDerived(str.toString()) else null
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
index c640baa..be30ea4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
@@ -34,8 +34,14 @@
     /** Observable list of current mobile subscriptions */
     val subscriptions: StateFlow<List<SubscriptionModel>>
 
-    /** Observable for the subscriptionId of the current mobile data connection */
-    val activeMobileDataSubscriptionId: StateFlow<Int>
+    /**
+     * Observable for the subscriptionId of the current mobile data connection. Null if we don't
+     * have a valid subscription id
+     */
+    val activeMobileDataSubscriptionId: StateFlow<Int?>
+
+    /** Repo that tracks the current [activeMobileDataSubscriptionId] */
+    val activeMobileDataRepository: StateFlow<MobileConnectionRepository?>
 
     /**
      * Observable event for when the active data sim switches but the group stays the same. E.g.,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
index 7038a3b..d54531a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
@@ -47,7 +47,6 @@
  * interface in its own repository, completely separate from the real version, while still using all
  * of the prod implementations for the rest of the pipeline (interactors and onward). Looks
  * something like this:
- *
  * ```
  * RealRepository
  *                 │
@@ -115,7 +114,7 @@
             .flatMapLatest { it.subscriptions }
             .stateIn(scope, SharingStarted.WhileSubscribed(), realRepository.subscriptions.value)
 
-    override val activeMobileDataSubscriptionId: StateFlow<Int> =
+    override val activeMobileDataSubscriptionId: StateFlow<Int?> =
         activeRepo
             .flatMapLatest { it.activeMobileDataSubscriptionId }
             .stateIn(
@@ -124,6 +123,15 @@
                 realRepository.activeMobileDataSubscriptionId.value
             )
 
+    override val activeMobileDataRepository: StateFlow<MobileConnectionRepository?> =
+        activeRepo
+            .flatMapLatest { it.activeMobileDataRepository }
+            .stateIn(
+                scope,
+                SharingStarted.WhileSubscribed(),
+                realRepository.activeMobileDataRepository.value
+            )
+
     override val activeSubChangedInGroupEvent: Flow<Unit> =
         activeRepo.flatMapLatest { it.activeSubChangedInGroupEvent }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
index 58cd36e..e924832 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
@@ -55,6 +55,7 @@
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.mapLatest
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.stateIn
@@ -121,6 +122,15 @@
                 subscriptions.value.firstOrNull()?.subscriptionId ?: INVALID_SUBSCRIPTION_ID
             )
 
+    override val activeMobileDataRepository: StateFlow<MobileConnectionRepository?> =
+        activeMobileDataSubscriptionId
+            .map { getRepoForSubId(it) }
+            .stateIn(
+                scope,
+                SharingStarted.WhileSubscribed(),
+                getRepoForSubId(activeMobileDataSubscriptionId.value)
+            )
+
     // TODO(b/261029387): consider adding a demo command for this
     override val activeSubChangedInGroupEvent: Flow<Unit> = flowOf()
 
@@ -240,7 +250,7 @@
 
         // This is always true here, because we split out disabled states at the data-source level
         connection.dataEnabled.value = true
-        connection.networkName.value = NetworkNameModel.Derived(state.name)
+        connection.networkName.value = NetworkNameModel.IntentDerived(state.name)
 
         connection.cdmaRoaming.value = state.roaming
         connection.connectionInfo.value = state.toMobileConnectionModel()
@@ -258,10 +268,13 @@
         maybeCreateSubscription(subId)
         carrierMergedSubId = subId
 
+        // TODO(b/261029387): until we have a command, use the most recent subId
+        defaultDataSubId.value = subId
+
         val connection = getRepoForSubId(subId)
         // This is always true here, because we split out disabled states at the data-source level
         connection.dataEnabled.value = true
-        connection.networkName.value = NetworkNameModel.Derived(CARRIER_MERGED_NAME)
+        connection.networkName.value = NetworkNameModel.IntentDerived(CARRIER_MERGED_NAME)
         connection.numberOfLevels.value = event.numberOfLevels
         connection.cdmaRoaming.value = false
         connection.connectionInfo.value = event.toMobileConnectionModel()
@@ -336,7 +349,10 @@
     }
 
     private fun FakeWifiEventModel.CarrierMerged.toMobileConnectionModel(): MobileConnectionModel {
-        return createCarrierMergedConnectionModel(this.level)
+        return createCarrierMergedConnectionModel(
+            this.level,
+            activity.toMobileDataActivityModel(),
+        )
     }
 
     private fun SignalIcon.MobileIconGroup?.toResolvedNetworkType(): ResolvedNetworkType {
@@ -371,5 +387,5 @@
 
     override val cdmaRoaming = MutableStateFlow(false)
 
-    override val networkName = MutableStateFlow(NetworkNameModel.Derived("demo network"))
+    override val networkName = MutableStateFlow(NetworkNameModel.IntentDerived("demo network"))
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt
index c783b12..938c734 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod
 
+import android.telephony.TelephonyManager
 import android.util.Log
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
@@ -37,7 +38,6 @@
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
 
@@ -54,10 +54,18 @@
 class CarrierMergedConnectionRepository(
     override val subId: Int,
     override val tableLogBuffer: TableLogBuffer,
-    defaultNetworkName: NetworkNameModel,
+    private val telephonyManager: TelephonyManager,
     @Application private val scope: CoroutineScope,
     val wifiRepository: WifiRepository,
 ) : MobileConnectionRepository {
+    init {
+        if (telephonyManager.subscriptionId != subId) {
+            throw IllegalStateException(
+                "CarrierMergedRepo: TelephonyManager should be created with subId($subId). " +
+                    "Found ${telephonyManager.subscriptionId} instead."
+            )
+        }
+    }
 
     /**
      * Outputs the carrier merged network to use, or null if we don't have a valid carrier merged
@@ -87,20 +95,28 @@
         }
 
     override val connectionInfo: StateFlow<MobileConnectionModel> =
-        network
-            .map { it.toMobileConnectionModel() }
+        combine(network, wifiRepository.wifiActivity) { network, activity ->
+                if (network == null) {
+                    MobileConnectionModel()
+                } else {
+                    createCarrierMergedConnectionModel(network.level, activity)
+                }
+            }
             .stateIn(scope, SharingStarted.WhileSubscribed(), MobileConnectionModel())
 
-    // TODO(b/238425913): Add logging to this class.
-    // TODO(b/238425913): Make sure SignalStrength.getEmptyState is used when appropriate.
+    override val cdmaRoaming: StateFlow<Boolean> = MutableStateFlow(ROAMING).asStateFlow()
 
-    // Carrier merged is never roaming.
-    override val cdmaRoaming: StateFlow<Boolean> = MutableStateFlow(false).asStateFlow()
-
-    // TODO(b/238425913): Fetch the carrier merged network name.
     override val networkName: StateFlow<NetworkNameModel> =
-        flowOf(defaultNetworkName)
-            .stateIn(scope, SharingStarted.WhileSubscribed(), defaultNetworkName)
+        network
+            // The SIM operator name should be the same throughout the lifetime of a subId, **but**
+            // it may not be available when this repo is created because it takes time to load. To
+            // be safe, we re-fetch it each time the network has changed.
+            .map { NetworkNameModel.SimDerived(telephonyManager.simOperatorName) }
+            .stateIn(
+                scope,
+                SharingStarted.WhileSubscribed(),
+                NetworkNameModel.SimDerived(telephonyManager.simOperatorName),
+            )
 
     override val numberOfLevels: StateFlow<Int> =
         wifiRepository.wifiNetwork
@@ -115,37 +131,24 @@
 
     override val dataEnabled: StateFlow<Boolean> = wifiRepository.isWifiEnabled
 
-    private fun WifiNetworkModel.CarrierMerged?.toMobileConnectionModel(): MobileConnectionModel {
-        if (this == null) {
-            return MobileConnectionModel()
-        }
-
-        return createCarrierMergedConnectionModel(level)
-    }
-
     companion object {
         /**
          * Creates an instance of [MobileConnectionModel] that represents a carrier merged network
-         * with the given [level].
+         * with the given [level] and [activity].
          */
-        fun createCarrierMergedConnectionModel(level: Int): MobileConnectionModel {
+        fun createCarrierMergedConnectionModel(
+            level: Int,
+            activity: DataActivityModel,
+        ): MobileConnectionModel {
             return MobileConnectionModel(
                 primaryLevel = level,
                 cdmaLevel = level,
-                // A [WifiNetworkModel.CarrierMerged] instance is always connected.
-                // (A [WifiNetworkModel.Inactive] represents a disconnected network.)
-                dataConnectionState = DataConnectionState.Connected,
-                // TODO(b/238425913): This should come from [WifiRepository.wifiActivity].
-                dataActivityDirection =
-                    DataActivityModel(
-                        hasActivityIn = false,
-                        hasActivityOut = false,
-                    ),
+                dataActivityDirection = activity,
+                // Here and below: These values are always the same for every carrier-merged
+                // connection.
                 resolvedNetworkType = ResolvedNetworkType.CarrierMergedNetworkType,
-                // Carrier merged is never roaming
-                isRoaming = false,
-
-                // TODO(b/238425913): Verify that these fields never change for carrier merged.
+                dataConnectionState = DataConnectionState.Connected,
+                isRoaming = ROAMING,
                 isEmergencyOnly = false,
                 operatorAlphaShort = null,
                 isInService = true,
@@ -153,24 +156,27 @@
                 carrierNetworkChangeActive = false,
             )
         }
+
+        // Carrier merged is never roaming
+        private const val ROAMING = false
     }
 
     @SysUISingleton
     class Factory
     @Inject
     constructor(
+        private val telephonyManager: TelephonyManager,
         @Application private val scope: CoroutineScope,
         private val wifiRepository: WifiRepository,
     ) {
         fun build(
             subId: Int,
             mobileLogger: TableLogBuffer,
-            defaultNetworkName: NetworkNameModel,
         ): MobileConnectionRepository {
             return CarrierMergedConnectionRepository(
                 subId,
                 mobileLogger,
-                defaultNetworkName,
+                telephonyManager.createForSubscriptionId(subId),
                 scope,
                 wifiRepository,
             )
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
index dd2cc92..a39ea0a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
@@ -86,7 +86,7 @@
     }
 
     private val carrierMergedRepo: MobileConnectionRepository by lazy {
-        carrierMergedRepoFactory.build(subId, tableLogBuffer, defaultNetworkName)
+        carrierMergedRepoFactory.build(subId, tableLogBuffer)
     }
 
     @VisibleForTesting
@@ -117,11 +117,22 @@
     override val connectionInfo =
         activeRepo
             .flatMapLatest { it.connectionInfo }
+            .logDiffsForTable(
+                tableLogBuffer,
+                columnPrefix = "",
+                initialValue = activeRepo.value.connectionInfo.value,
+            )
             .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.connectionInfo.value)
 
     override val dataEnabled =
         activeRepo
             .flatMapLatest { it.dataEnabled }
+            .logDiffsForTable(
+                tableLogBuffer,
+                columnPrefix = "",
+                columnName = "dataEnabled",
+                initialValue = activeRepo.value.dataEnabled.value,
+            )
             .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.dataEnabled.value)
 
     override val numberOfLevels =
@@ -132,6 +143,11 @@
     override val networkName =
         activeRepo
             .flatMapLatest { it.networkName }
+            .logDiffsForTable(
+                tableLogBuffer,
+                columnPrefix = "",
+                initialValue = activeRepo.value.networkName.value,
+            )
             .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.networkName.value)
 
     class Factory
@@ -168,7 +184,7 @@
             const val MOBILE_CONNECTION_BUFFER_SIZE = 100
 
             /** Returns a log buffer name for a mobile connection with the given [subId]. */
-            fun tableBufferLogName(subId: Int): String = "MobileConnectionLog [$subId]"
+            fun tableBufferLogName(subId: Int): String = "MobileConnectionLog[$subId]"
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
index 76fef35..dcce0ea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
@@ -36,7 +36,6 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.log.table.logDiffsForTable
 import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType
@@ -62,7 +61,7 @@
 import kotlinx.coroutines.flow.SharedFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.mapLatest
 import kotlinx.coroutines.flow.mapNotNull
@@ -87,13 +86,13 @@
     private val mobileMappingsProxy: MobileMappingsProxy,
     bgDispatcher: CoroutineDispatcher,
     logger: ConnectivityPipelineLogger,
-    mobileLogger: TableLogBuffer,
+    override val tableLogBuffer: TableLogBuffer,
     scope: CoroutineScope,
 ) : MobileConnectionRepository {
     init {
         if (telephonyManager.subscriptionId != subId) {
             throw IllegalStateException(
-                "TelephonyManager should be created with subId($subId). " +
+                "MobileRepo: TelephonyManager should be created with subId($subId). " +
                     "Found ${telephonyManager.subscriptionId} instead."
             )
         }
@@ -101,8 +100,6 @@
 
     private val telephonyCallbackEvent = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
 
-    override val tableLogBuffer: TableLogBuffer = mobileLogger
-
     /**
      * This flow defines the single shared connection to system_server via TelephonyCallback. Any
      * new callback should be added to this listener and funneled through callbackEvents via a data
@@ -243,11 +240,6 @@
         val initial = MobileConnectionModel()
         callbackEvents
             .scan(initial, ::updateConnectionState)
-            .logDiffsForTable(
-                mobileLogger,
-                columnPrefix = "MobileConnection ($subId)",
-                initialValue = initial,
-            )
             .stateIn(scope, SharingStarted.WhileSubscribed(), initial)
     }
 
@@ -276,33 +268,20 @@
 
     override val networkName: StateFlow<NetworkNameModel> =
         broadcastDispatcher
-            .broadcastFlow(IntentFilter(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED)) {
-                intent,
-                _ ->
-                if (intent.getIntExtra(EXTRA_SUBSCRIPTION_ID, INVALID_SUBSCRIPTION_ID) != subId) {
-                    defaultNetworkName
-                } else {
-                    intent.toNetworkNameModel(networkNameSeparator) ?: defaultNetworkName
-                }
-            }
-            .distinctUntilChanged()
-            .logDiffsForTable(
-                mobileLogger,
-                columnPrefix = "",
-                initialValue = defaultNetworkName,
+            .broadcastFlow(
+                filter = IntentFilter(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED),
+                map = { intent, _ -> intent },
             )
+            .filter { intent ->
+                intent.getIntExtra(EXTRA_SUBSCRIPTION_ID, INVALID_SUBSCRIPTION_ID) == subId
+            }
+            .map { intent -> intent.toNetworkNameModel(networkNameSeparator) ?: defaultNetworkName }
             .stateIn(scope, SharingStarted.WhileSubscribed(), defaultNetworkName)
 
     override val dataEnabled = run {
         val initial = telephonyManager.isDataConnectionAllowed
         callbackEvents
             .mapNotNull { (it as? CallbackEvent.OnDataEnabledChanged)?.enabled }
-            .logDiffsForTable(
-                mobileLogger,
-                columnPrefix = "",
-                columnName = "dataEnabled",
-                initialValue = initial
-            )
             .stateIn(scope, SharingStarted.WhileSubscribed(), initial)
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
index 10f48a3..c9049d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
@@ -42,6 +42,9 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
+import com.android.systemui.statusbar.pipeline.dagger.MobileSummaryLog
 import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
@@ -82,6 +85,7 @@
     private val subscriptionManager: SubscriptionManager,
     private val telephonyManager: TelephonyManager,
     private val logger: ConnectivityPipelineLogger,
+    @MobileSummaryLog private val tableLogger: TableLogBuffer,
     mobileMappingsProxy: MobileMappingsProxy,
     broadcastDispatcher: BroadcastDispatcher,
     private val context: Context,
@@ -114,6 +118,12 @@
                 }
             }
             .distinctUntilChanged()
+            .logDiffsForTable(
+                tableLogger,
+                LOGGING_PREFIX,
+                columnName = "carrierMergedSubId",
+                initialValue = null,
+            )
             .stateIn(scope, started = SharingStarted.WhileSubscribed(), null)
 
     private val mobileSubscriptionsChangeEvent: Flow<Unit> = conflatedCallbackFlow {
@@ -139,17 +149,26 @@
     override val subscriptions: StateFlow<List<SubscriptionModel>> =
         merge(mobileSubscriptionsChangeEvent, carrierMergedSubId)
             .mapLatest { fetchSubscriptionsList().map { it.toSubscriptionModel() } }
-            .logInputChange(logger, "onSubscriptionsChanged")
             .onEach { infos -> updateRepos(infos) }
+            .distinctUntilChanged()
+            .logDiffsForTable(
+                tableLogger,
+                LOGGING_PREFIX,
+                columnName = "subscriptions",
+                initialValue = listOf(),
+            )
             .stateIn(scope, started = SharingStarted.WhileSubscribed(), listOf())
 
-    /** StateFlow that keeps track of the current active mobile data subscription */
-    override val activeMobileDataSubscriptionId: StateFlow<Int> =
+    override val activeMobileDataSubscriptionId: StateFlow<Int?> =
         conflatedCallbackFlow {
                 val callback =
                     object : TelephonyCallback(), ActiveDataSubscriptionIdListener {
                         override fun onActiveDataSubscriptionIdChanged(subId: Int) {
-                            trySend(subId)
+                            if (subId != INVALID_SUBSCRIPTION_ID) {
+                                trySend(subId)
+                            } else {
+                                trySend(null)
+                            }
                         }
                     }
 
@@ -157,8 +176,24 @@
                 awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
             }
             .distinctUntilChanged()
-            .logInputChange(logger, "onActiveDataSubscriptionIdChanged")
-            .stateIn(scope, started = SharingStarted.WhileSubscribed(), INVALID_SUBSCRIPTION_ID)
+            .logDiffsForTable(
+                tableLogger,
+                LOGGING_PREFIX,
+                columnName = "activeSubId",
+                initialValue = INVALID_SUBSCRIPTION_ID,
+            )
+            .stateIn(scope, started = SharingStarted.WhileSubscribed(), null)
+
+    override val activeMobileDataRepository =
+        activeMobileDataSubscriptionId
+            .map { activeSubId ->
+                if (activeSubId == null) {
+                    null
+                } else {
+                    getOrCreateRepoForSubId(activeSubId)
+                }
+            }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), null)
 
     private val defaultDataSubIdChangeEvent: MutableSharedFlow<Unit> =
         MutableSharedFlow(extraBufferCapacity = 1)
@@ -171,7 +206,12 @@
                 intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY, INVALID_SUBSCRIPTION_ID)
             }
             .distinctUntilChanged()
-            .logInputChange(logger, "ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED")
+            .logDiffsForTable(
+                tableLogger,
+                LOGGING_PREFIX,
+                columnName = "defaultSubId",
+                initialValue = SubscriptionManager.getDefaultDataSubscriptionId(),
+            )
             .onEach { defaultDataSubIdChangeEvent.tryEmit(Unit) }
             .stateIn(
                 scope,
@@ -214,10 +254,13 @@
             )
         }
 
-        return subIdRepositoryCache[subId]
-            ?: createRepositoryForSubId(subId).also { subIdRepositoryCache[subId] = it }
+        return getOrCreateRepoForSubId(subId)
     }
 
+    private fun getOrCreateRepoForSubId(subId: Int) =
+        subIdRepositoryCache[subId]
+            ?: createRepositoryForSubId(subId).also { subIdRepositoryCache[subId] = it }
+
     @SuppressLint("MissingPermission")
     override val defaultMobileNetworkConnectivity: StateFlow<MobileConnectivityModel> =
         conflatedCallbackFlow {
@@ -247,7 +290,11 @@
                 awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
             }
             .distinctUntilChanged()
-            .logInputChange(logger, "defaultMobileNetworkConnectivity")
+            .logDiffsForTable(
+                tableLogger,
+                columnPrefix = "$LOGGING_PREFIX.defaultConnection",
+                initialValue = MobileConnectivityModel(),
+            )
             .stateIn(scope, SharingStarted.WhileSubscribed(), MobileConnectivityModel())
 
     /**
@@ -262,7 +309,9 @@
     override val activeSubChangedInGroupEvent =
         activeMobileDataSubscriptionId
             .pairwise()
-            .mapNotNull { (prevVal: Int, newVal: Int) ->
+            .mapNotNull { (prevVal: Int?, newVal: Int?) ->
+                if (prevVal == null || newVal == null) return@mapNotNull null
+
                 val prevSub = subscriptionManager.getActiveSubscriptionInfo(prevVal)?.groupUuid
                 val nextSub = subscriptionManager.getActiveSubscriptionInfo(newVal)?.groupUuid
 
@@ -270,15 +319,7 @@
             }
             .flowOn(bgDispatcher)
 
-    private fun isValidSubId(subId: Int): Boolean {
-        subscriptions.value.forEach {
-            if (it.subscriptionId == subId) {
-                return true
-            }
-        }
-
-        return false
-    }
+    private fun isValidSubId(subId: Int): Boolean = checkSub(subId, subscriptions.value)
 
     @VisibleForTesting fun getSubIdRepoCache() = subIdRepositoryCache
 
@@ -305,12 +346,27 @@
     private fun dropUnusedReposFromCache(newInfos: List<SubscriptionModel>) {
         // Remove any connection repository from the cache that isn't in the new set of IDs. They
         // will get garbage collected once their subscribers go away
-        val currentValidSubscriptionIds = newInfos.map { it.subscriptionId }
-
         subIdRepositoryCache =
-            subIdRepositoryCache
-                .filter { currentValidSubscriptionIds.contains(it.key) }
-                .toMutableMap()
+            subIdRepositoryCache.filter { checkSub(it.key, newInfos) }.toMutableMap()
+    }
+
+    /**
+     * True if the checked subId is in the list of current subs or the active mobile data subId
+     *
+     * @param checkedSubs the list to validate [subId] against. To invalidate the cache, pass in the
+     * new subscription list. Otherwise use [subscriptions.value] to validate a subId against the
+     * current known subscriptions
+     */
+    private fun checkSub(subId: Int, checkedSubs: List<SubscriptionModel>): Boolean {
+        if (activeMobileDataSubscriptionId.value == subId) return true
+
+        checkedSubs.forEach {
+            if (it.subscriptionId == subId) {
+                return true
+            }
+        }
+
+        return false
     }
 
     private suspend fun fetchSubscriptionsList(): List<SubscriptionInfo> =
@@ -321,4 +377,8 @@
             subscriptionId = subscriptionId,
             isOpportunistic = isOpportunistic,
         )
+
+    companion object {
+        private const val LOGGING_PREFIX = "Repo"
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
index 9cdff96..7b0f952 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
@@ -33,7 +33,9 @@
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.stateIn
 
 interface MobileIconInteractor {
@@ -109,6 +111,9 @@
 
     /** Based on [CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL], either 4 or 5 */
     val numberOfLevels: StateFlow<Int>
+
+    /** See [MobileIconsInteractor.isForceHidden]. */
+    val isForceHidden: Flow<Boolean>
 }
 
 /** Interactor for a single mobile connection. This connection _should_ have one subscription ID */
@@ -124,6 +129,7 @@
     defaultMobileIconGroup: StateFlow<MobileIconGroup>,
     defaultDataSubId: StateFlow<Int>,
     override val isDefaultConnectionFailed: StateFlow<Boolean>,
+    override val isForceHidden: Flow<Boolean>,
     connectionRepository: MobileConnectionRepository,
 ) : MobileIconInteractor {
     private val connectionInfo = connectionRepository.connectionInfo
@@ -152,7 +158,7 @@
                 if (
                     networkName is NetworkNameModel.Default && connection.operatorAlphaShort != null
                 ) {
-                    NetworkNameModel.Derived(connection.operatorAlphaShort)
+                    NetworkNameModel.IntentDerived(connection.operatorAlphaShort)
                 } else {
                     networkName
                 }
@@ -181,6 +187,16 @@
                     else -> mapping[info.resolvedNetworkType.lookupKey] ?: defaultGroup
                 }
             }
+            .distinctUntilChanged()
+            .onEach {
+                // Doesn't use [logDiffsForTable] because [MobileIconGroup] can't implement the
+                // [Diffable] interface.
+                tableLogBuffer.logChange(
+                    prefix = "",
+                    columnName = "networkTypeIcon",
+                    value = it.name
+                )
+            }
             .stateIn(scope, SharingStarted.WhileSubscribed(), defaultMobileIconGroup.value)
 
     override val isEmergencyOnly: StateFlow<Boolean> =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
index 0e4a432..72d5113 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
@@ -18,17 +18,21 @@
 
 import android.telephony.CarrierConfigManager
 import android.telephony.SubscriptionManager
-import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
 import com.android.settingslib.SignalIcon.MobileIconGroup
 import com.android.settingslib.mobile.TelephonyIcons
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
+import com.android.systemui.statusbar.pipeline.dagger.MobileSummaryLog
 import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
+import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
 import com.android.systemui.util.CarrierConfigTracker
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
@@ -42,8 +46,8 @@
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.mapLatest
-import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.flow.transformLatest
 
@@ -88,6 +92,10 @@
     val isDefaultConnectionFailed: StateFlow<Boolean>
     /** True once the user has been set up */
     val isUserSetup: StateFlow<Boolean>
+
+    /** True if we're configured to force-hide the mobile icons and false otherwise. */
+    val isForceHidden: Flow<Boolean>
+
     /**
      * Vends out a [MobileIconInteractor] tracking the [MobileConnectionRepository] for the given
      * subId. Will throw if the ID is invalid
@@ -104,25 +112,13 @@
     private val mobileConnectionsRepo: MobileConnectionsRepository,
     private val carrierConfigTracker: CarrierConfigTracker,
     private val logger: ConnectivityPipelineLogger,
+    @MobileSummaryLog private val tableLogger: TableLogBuffer,
+    connectivityRepository: ConnectivityRepository,
     userSetupRepo: UserSetupRepository,
     @Application private val scope: CoroutineScope,
 ) : MobileIconsInteractor {
-    private val activeMobileDataSubscriptionId =
-        mobileConnectionsRepo.activeMobileDataSubscriptionId
-
-    private val activeMobileDataConnectionRepo: StateFlow<MobileConnectionRepository?> =
-        activeMobileDataSubscriptionId
-            .mapLatest { activeId ->
-                if (activeId == INVALID_SUBSCRIPTION_ID) {
-                    null
-                } else {
-                    mobileConnectionsRepo.getRepoForSubId(activeId)
-                }
-            }
-            .stateIn(scope, SharingStarted.WhileSubscribed(), null)
-
     override val activeDataConnectionHasDataEnabled: StateFlow<Boolean> =
-        activeMobileDataConnectionRepo
+        mobileConnectionsRepo.activeMobileDataRepository
             .flatMapLatest { it?.dataEnabled ?: flowOf(false) }
             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
 
@@ -143,37 +139,45 @@
      * and by checking which subscription is opportunistic, or which one is active.
      */
     override val filteredSubscriptions: Flow<List<SubscriptionModel>> =
-        combine(unfilteredSubscriptions, activeMobileDataSubscriptionId) { unfilteredSubs, activeId
-            ->
-            // Based on the old logic,
-            if (unfilteredSubs.size != 2) {
-                return@combine unfilteredSubs
-            }
+        combine(
+                unfilteredSubscriptions,
+                mobileConnectionsRepo.activeMobileDataSubscriptionId,
+            ) { unfilteredSubs, activeId ->
+                // Based on the old logic,
+                if (unfilteredSubs.size != 2) {
+                    return@combine unfilteredSubs
+                }
 
-            val info1 = unfilteredSubs[0]
-            val info2 = unfilteredSubs[1]
-            // If both subscriptions are primary, show both
-            if (!info1.isOpportunistic && !info2.isOpportunistic) {
-                return@combine unfilteredSubs
-            }
+                val info1 = unfilteredSubs[0]
+                val info2 = unfilteredSubs[1]
+                // If both subscriptions are primary, show both
+                if (!info1.isOpportunistic && !info2.isOpportunistic) {
+                    return@combine unfilteredSubs
+                }
 
-            // NOTE: at this point, we are now returning a single SubscriptionInfo
+                // NOTE: at this point, we are now returning a single SubscriptionInfo
 
-            // If carrier required, always show the icon of the primary subscription.
-            // Otherwise, show whichever subscription is currently active for internet.
-            if (carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault) {
-                // return the non-opportunistic info
-                return@combine if (info1.isOpportunistic) listOf(info2) else listOf(info1)
-            } else {
-                return@combine if (info1.subscriptionId == activeId) {
-                    listOf(info1)
+                // If carrier required, always show the icon of the primary subscription.
+                // Otherwise, show whichever subscription is currently active for internet.
+                if (carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault) {
+                    // return the non-opportunistic info
+                    return@combine if (info1.isOpportunistic) listOf(info2) else listOf(info1)
                 } else {
-                    listOf(info2)
+                    return@combine if (info1.subscriptionId == activeId) {
+                        listOf(info1)
+                    } else {
+                        listOf(info2)
+                    }
                 }
             }
-        }
             .distinctUntilChanged()
-            .onEach { logger.logFilteredSubscriptionsChanged(it) }
+            .logDiffsForTable(
+                tableLogger,
+                LOGGING_PREFIX,
+                columnName = "filteredSubscriptions",
+                initialValue = listOf(),
+            )
+            .stateIn(scope, SharingStarted.WhileSubscribed(), listOf())
 
     override val defaultDataSubId = mobileConnectionsRepo.defaultDataSubId
 
@@ -195,6 +199,12 @@
                 delay(2000)
                 emit(false)
             }
+            .logDiffsForTable(
+                tableLogger,
+                LOGGING_PREFIX,
+                columnName = "forcingValidation",
+                initialValue = false,
+            )
             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
 
     override val defaultMobileNetworkConnectivity: StateFlow<MobileConnectivityModel> =
@@ -211,6 +221,12 @@
                     networkConnectivity
                 }
             }
+            .distinctUntilChanged()
+            .logDiffsForTable(
+                tableLogger,
+                columnPrefix = "$LOGGING_PREFIX.defaultConnection",
+                initialValue = mobileConnectionsRepo.defaultMobileNetworkConnectivity.value,
+            )
             .stateIn(
                 scope,
                 SharingStarted.WhileSubscribed(),
@@ -259,10 +275,21 @@
                     !connectivityModel.isValidated
                 }
             }
+            .logDiffsForTable(
+                tableLogger,
+                LOGGING_PREFIX,
+                columnName = "isDefaultConnectionFailed",
+                initialValue = false,
+            )
             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
 
     override val isUserSetup: StateFlow<Boolean> = userSetupRepo.isUserSetupFlow
 
+    override val isForceHidden: Flow<Boolean> =
+        connectivityRepository.forceHiddenSlots
+            .map { it.contains(ConnectivitySlot.MOBILE) }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
     /** Vends out new [MobileIconInteractor] for a particular subId */
     override fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor =
         MobileIconInteractorImpl(
@@ -275,6 +302,11 @@
             defaultMobileIconGroup,
             defaultDataSubId,
             isDefaultConnectionFailed,
+            isForceHidden,
             mobileConnectionsRepo.getRepoForSubId(subId),
         )
+
+    companion object {
+        private const val LOGGING_PREFIX = "Intr"
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
index a4b2abc..db585e6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
@@ -91,10 +91,17 @@
                     }
                 }
 
+                launch { viewModel.isVisible.collect { isVisible -> view.isVisible = isVisible } }
+
                 // Set the icon for the triangle
                 launch {
-                    viewModel.iconId.distinctUntilChanged().collect { iconId ->
-                        mobileDrawable.level = iconId
+                    viewModel.icon.distinctUntilChanged().collect { icon ->
+                        mobileDrawable.level =
+                            SignalDrawable.getState(
+                                icon.level,
+                                icon.numberOfLevels,
+                                icon.showExclamationMark,
+                            )
                     }
                 }
 
@@ -148,8 +155,7 @@
 
         return object : ModernStatusBarViewBinding {
             override fun getShouldIconBeVisible(): Boolean {
-                // If this view model exists, then the icon should be visible.
-                return true
+                return viewModel.isVisible.value
             }
 
             override fun onVisibilityStateChanged(@StatusBarIconView.VisibleState state: Int) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModel.kt
new file mode 100644
index 0000000..16e1766
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModel.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.ui.model
+
+import com.android.systemui.log.table.Diffable
+import com.android.systemui.log.table.TableRowLogger
+
+/** A model that will be consumed by [SignalDrawable] to show the mobile triangle icon. */
+data class SignalIconModel(
+    val level: Int,
+    val numberOfLevels: Int,
+    val showExclamationMark: Boolean,
+) : Diffable<SignalIconModel> {
+    // TODO(b/267767715): Can we implement [logDiffs] and [logFull] generically for data classes?
+    override fun logDiffs(prevVal: SignalIconModel, row: TableRowLogger) {
+        if (prevVal.level != level) {
+            row.logChange(COL_LEVEL, level)
+        }
+        if (prevVal.numberOfLevels != numberOfLevels) {
+            row.logChange(COL_NUM_LEVELS, numberOfLevels)
+        }
+        if (prevVal.showExclamationMark != showExclamationMark) {
+            row.logChange(COL_SHOW_EXCLAMATION, showExclamationMark)
+        }
+    }
+
+    override fun logFull(row: TableRowLogger) {
+        row.logChange(COL_LEVEL, level)
+        row.logChange(COL_NUM_LEVELS, numberOfLevels)
+        row.logChange(COL_SHOW_EXCLAMATION, showExclamationMark)
+    }
+
+    companion object {
+        /** Creates a [SignalIconModel] representing an empty and invalidated state. */
+        fun createEmptyState(numberOfLevels: Int) =
+            SignalIconModel(level = 0, numberOfLevels, showExclamationMark = true)
+
+        private const val COL_LEVEL = "level"
+        private const val COL_NUM_LEVELS = "numLevels"
+        private const val COL_SHOW_EXCLAMATION = "showExclamation"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
index 9e2024a..0496278 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
@@ -22,10 +22,11 @@
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.log.table.logDiffsForTable
+import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractor
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
+import com.android.systemui.statusbar.pipeline.mobile.ui.model.SignalIconModel
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
-import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
 import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -37,14 +38,14 @@
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.mapLatest
-import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.stateIn
 
 /** Common interface for all of the location-based mobile icon view models. */
 interface MobileIconViewModelCommon {
     val subscriptionId: Int
-    /** An int consumable by [SignalDrawable] for display */
-    val iconId: Flow<Int>
+    /** True if this view should be visible at all. */
+    val isVisible: StateFlow<Boolean>
+    val icon: Flow<SignalIconModel>
     val contentDescription: Flow<ContentDescription>
     val roaming: Flow<Boolean>
     /** The RAT icon (LTE, 3G, 5G, etc) to be displayed. Null if we shouldn't show anything */
@@ -73,7 +74,7 @@
 constructor(
     override val subscriptionId: Int,
     iconInteractor: MobileIconInteractor,
-    logger: ConnectivityPipelineLogger,
+    airplaneModeInteractor: AirplaneModeInteractor,
     constants: ConnectivityConstants,
     scope: CoroutineScope,
 ) : MobileIconViewModelCommon {
@@ -81,8 +82,28 @@
     private val showExclamationMark: Flow<Boolean> =
         iconInteractor.isDefaultDataEnabled.mapLatest { !it }
 
-    override val iconId: Flow<Int> = run {
-        val initial = SignalDrawable.getEmptyState(iconInteractor.numberOfLevels.value)
+    override val isVisible: StateFlow<Boolean> =
+        if (!constants.hasDataCapabilities) {
+                flowOf(false)
+            } else {
+                combine(
+                    airplaneModeInteractor.isAirplaneMode,
+                    iconInteractor.isForceHidden,
+                ) { isAirplaneMode, isForceHidden ->
+                    !isAirplaneMode && !isForceHidden
+                }
+            }
+            .distinctUntilChanged()
+            .logDiffsForTable(
+                iconInteractor.tableLogBuffer,
+                columnPrefix = "",
+                columnName = "visible",
+                initialValue = false,
+            )
+            .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+    override val icon: Flow<SignalIconModel> = run {
+        val initial = SignalIconModel.createEmptyState(iconInteractor.numberOfLevels.value)
         combine(
                 iconInteractor.level,
                 iconInteractor.numberOfLevels,
@@ -90,16 +111,15 @@
                 iconInteractor.isInService,
             ) { level, numberOfLevels, showExclamationMark, isInService ->
                 if (!isInService) {
-                    SignalDrawable.getEmptyState(numberOfLevels)
+                    SignalIconModel.createEmptyState(numberOfLevels)
                 } else {
-                    SignalDrawable.getState(level, numberOfLevels, showExclamationMark)
+                    SignalIconModel(level, numberOfLevels, showExclamationMark)
                 }
             }
             .distinctUntilChanged()
             .logDiffsForTable(
                 iconInteractor.tableLogBuffer,
-                columnPrefix = "",
-                columnName = "iconId",
+                columnPrefix = "icon",
                 initialValue = initial,
             )
             .stateIn(scope, SharingStarted.WhileSubscribed(), initial)
@@ -124,14 +144,22 @@
 
     private val showNetworkTypeIcon: Flow<Boolean> =
         combine(
-            iconInteractor.isDataConnected,
-            iconInteractor.isDataEnabled,
-            iconInteractor.isDefaultConnectionFailed,
-            iconInteractor.alwaysShowDataRatIcon,
-            iconInteractor.isConnected,
-        ) { dataConnected, dataEnabled, failedConnection, alwaysShow, connected ->
-            alwaysShow || (dataConnected && dataEnabled && !failedConnection && connected)
-        }
+                iconInteractor.isDataConnected,
+                iconInteractor.isDataEnabled,
+                iconInteractor.isDefaultConnectionFailed,
+                iconInteractor.alwaysShowDataRatIcon,
+                iconInteractor.isConnected,
+            ) { dataConnected, dataEnabled, failedConnection, alwaysShow, connected ->
+                alwaysShow || (dataConnected && dataEnabled && !failedConnection && connected)
+            }
+            .distinctUntilChanged()
+            .logDiffsForTable(
+                iconInteractor.tableLogBuffer,
+                columnPrefix = "",
+                columnName = "showNetworkTypeIcon",
+                initialValue = false,
+            )
+            .stateIn(scope, SharingStarted.WhileSubscribed(), false)
 
     override val networkTypeIcon: Flow<Icon?> =
         combine(
@@ -149,14 +177,6 @@
                 }
             }
             .distinctUntilChanged()
-            .onEach {
-                // This is done as an onEach side effect since Icon is not Diffable (yet)
-                iconInteractor.tableLogBuffer.logChange(
-                    prefix = "",
-                    columnName = "networkTypeIcon",
-                    value = it.toString(),
-                )
-            }
             .stateIn(scope, SharingStarted.WhileSubscribed(), null)
 
     override val roaming: StateFlow<Boolean> =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
index 24370d2..185b668 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
@@ -17,9 +17,11 @@
 package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
 
 import androidx.annotation.VisibleForTesting
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.statusbar.phone.StatusBarLocation
 import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
 import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
@@ -39,6 +41,7 @@
 constructor(
     val subscriptionIdsFlow: StateFlow<List<Int>>,
     private val interactor: MobileIconsInteractor,
+    private val airplaneModeInteractor: AirplaneModeInteractor,
     private val logger: ConnectivityPipelineLogger,
     private val constants: ConnectivityConstants,
     @Application private val scope: CoroutineScope,
@@ -56,7 +59,7 @@
                 ?: MobileIconViewModel(
                         subId,
                         interactor.createMobileConnectionInteractorForSubId(subId),
-                        logger,
+                        airplaneModeInteractor,
                         constants,
                         scope,
                     )
@@ -74,10 +77,12 @@
         subIdsToRemove.forEach { mobileIconSubIdCache.remove(it) }
     }
 
+    @SysUISingleton
     class Factory
     @Inject
     constructor(
         private val interactor: MobileIconsInteractor,
+        private val airplaneModeInteractor: AirplaneModeInteractor,
         private val logger: ConnectivityPipelineLogger,
         private val constants: ConnectivityConstants,
         @Application private val scope: CoroutineScope,
@@ -87,6 +92,7 @@
             return MobileIconsViewModel(
                 subscriptionIdsFlow,
                 interactor,
+                airplaneModeInteractor,
                 logger,
                 constants,
                 scope,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt
index 6796a94..45036969 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt
@@ -204,15 +204,6 @@
 
     // TODO(b/238425913): We should split this class into mobile-specific and wifi-specific loggers.
 
-    fun logFilteredSubscriptionsChanged(subs: List<SubscriptionModel>) {
-        buffer.log(
-            SB_LOGGING_TAG,
-            LogLevel.INFO,
-            { str1 = subs.toString() },
-            { "Filtered subscriptions updated: $str1" },
-        )
-    }
-
     fun logUiAdapterSubIdsUpdated(subs: List<Int>) {
         buffer.log(
             SB_LOGGING_TAG,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoModeWifiDataSource.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoModeWifiDataSource.kt
index caac8fa..7d2501ca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoModeWifiDataSource.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoModeWifiDataSource.kt
@@ -53,7 +53,7 @@
 
     private fun Bundle.activeWifiEvent(): FakeWifiEventModel.Wifi {
         val level = getString("level")?.toInt()
-        val activity = getString("activity")?.toActivity()
+        val activity = getString("activity").toActivity()
         val ssid = getString("ssid")
         val validated = getString("fully").toBoolean()
 
@@ -69,11 +69,12 @@
         val subId = getString("slot")?.toInt() ?: DEFAULT_CARRIER_MERGED_SUB_ID
         val level = getString("level")?.toInt() ?: 0
         val numberOfLevels = getString("numlevels")?.toInt() ?: DEFAULT_NUM_LEVELS
+        val activity = getString("activity").toActivity()
 
-        return FakeWifiEventModel.CarrierMerged(subId, level, numberOfLevels)
+        return FakeWifiEventModel.CarrierMerged(subId, level, numberOfLevels, activity)
     }
 
-    private fun String.toActivity(): Int =
+    private fun String?.toActivity(): Int =
         when (this) {
             "inout" -> WifiManager.TrafficStateCallback.DATA_ACTIVITY_INOUT
             "in" -> WifiManager.TrafficStateCallback.DATA_ACTIVITY_IN
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt
index e161b3e..a19c3c3e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt
@@ -80,17 +80,14 @@
     private fun processEnabledWifiState(event: FakeWifiEventModel.Wifi) {
         _isWifiEnabled.value = true
         _isWifiDefault.value = true
-        _wifiActivity.value =
-            event.activity?.toWifiDataActivityModel()
-                ?: DataActivityModel(hasActivityIn = false, hasActivityOut = false)
+        _wifiActivity.value = event.activity.toWifiDataActivityModel()
         _wifiNetwork.value = event.toWifiNetworkModel()
     }
 
     private fun processCarrierMergedWifiState(event: FakeWifiEventModel.CarrierMerged) {
         _isWifiEnabled.value = true
         _isWifiDefault.value = true
-        // TODO(b/238425913): Support activity in demo mode.
-        _wifiActivity.value = DataActivityModel(hasActivityIn = false, hasActivityOut = false)
+        _wifiActivity.value = event.activity.toWifiDataActivityModel()
         _wifiNetwork.value = event.toCarrierMergedModel()
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/model/FakeWifiEventModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/model/FakeWifiEventModel.kt
index 518f8ce..f5035cbc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/model/FakeWifiEventModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/model/FakeWifiEventModel.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model
 
+import android.telephony.Annotation
+
 /**
  * Model for demo wifi commands, ported from [NetworkControllerImpl]
  *
@@ -24,7 +26,7 @@
 sealed interface FakeWifiEventModel {
     data class Wifi(
         val level: Int?,
-        val activity: Int?,
+        @Annotation.DataActivityType val activity: Int,
         val ssid: String?,
         val validated: Boolean?,
     ) : FakeWifiEventModel
@@ -33,6 +35,7 @@
         val subscriptionId: Int,
         val level: Int,
         val numberOfLevels: Int,
+        @Annotation.DataActivityType val activity: Int,
     ) : FakeWifiEventModel
 
     object WifiDisabled : FakeWifiEventModel
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsController.kt
index e2bebbe..f0949ac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsController.kt
@@ -25,6 +25,8 @@
          * If controls become available, initiate this callback with the desired position
          */
         fun onControlsUpdate(position: Int?)
+
+        fun removeControlsAutoTracker()
     }
 
     /** Add callback, supporting only a single callback at once */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt
index 341eb3b..4950482 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt
@@ -21,16 +21,15 @@
 import android.content.SharedPreferences
 import android.provider.Settings
 import android.util.Log
-
 import com.android.systemui.R
 import com.android.systemui.controls.ControlsServiceInfo
 import com.android.systemui.controls.dagger.ControlsComponent
 import com.android.systemui.controls.management.ControlsListingController
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.settings.UserContextProvider
+import com.android.systemui.statusbar.phone.AutoTileManager
 import com.android.systemui.statusbar.policy.DeviceControlsController.Callback
 import com.android.systemui.util.settings.SecureSettings
-
 import javax.inject.Inject
 
 /**
@@ -87,6 +86,10 @@
      * incorrect.
      */
     override fun setCallback(callback: Callback) {
+        if (!controlsComponent.isEnabled()) {
+            callback.removeControlsAutoTracker()
+            return
+        }
         // Treat any additional call as a reset before recalculating
         removeCallback()
         this.callback = callback
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerExt.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerExt.kt
new file mode 100644
index 0000000..5e36750
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerExt.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy
+
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * Returns a [Flow] that emits events whenever a [NotificationEntry] enters or exists the "heads up"
+ * state.
+ */
+val HeadsUpManager.headsUpEvents: Flow<Pair<NotificationEntry, Boolean>>
+    get() = conflatedCallbackFlow {
+        val listener =
+            object : OnHeadsUpChangedListener {
+                override fun onHeadsUpStateChanged(entry: NotificationEntry, isHeadsUp: Boolean) {
+                    trySend(entry to isHeadsUp)
+                }
+            }
+        addListener(listener)
+        awaitClose { removeListener(listener) }
+    }
diff --git a/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt b/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt
index b22af3b..3f54aebf 100644
--- a/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt
@@ -25,6 +25,7 @@
 import android.util.ArrayMap
 import android.util.Log
 import android.view.InputDevice
+import com.android.internal.logging.UiEventLogger
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.flags.FeatureFlags
@@ -47,6 +48,7 @@
     @Background private val handler: Handler,
     @Background private val executor: Executor,
     private val featureFlags: FeatureFlags,
+    private val uiEventLogger: UiEventLogger,
 ) :
     InputManager.InputDeviceListener,
     InputManager.InputDeviceBatteryListener,
@@ -186,6 +188,7 @@
     }
 
     private fun onStylusBluetoothConnected(btAddress: String) {
+        uiEventLogger.log(StylusUiEvent.BLUETOOTH_STYLUS_CONNECTED)
         val device: BluetoothDevice = bluetoothAdapter?.getRemoteDevice(btAddress) ?: return
         try {
             bluetoothAdapter.addOnMetadataChangedListener(device, executor, this)
@@ -195,6 +198,7 @@
     }
 
     private fun onStylusBluetoothDisconnected(btAddress: String) {
+        uiEventLogger.log(StylusUiEvent.BLUETOOTH_STYLUS_DISCONNECTED)
         val device: BluetoothDevice = bluetoothAdapter?.getRemoteDevice(btAddress) ?: return
         try {
             bluetoothAdapter.removeOnMetadataChangedListener(device, this)
diff --git a/packages/SystemUI/src/com/android/systemui/stylus/StylusUiEvent.kt b/packages/SystemUI/src/com/android/systemui/stylus/StylusUiEvent.kt
new file mode 100644
index 0000000..99da4ce
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/stylus/StylusUiEvent.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.stylus
+
+import com.android.internal.logging.UiEvent
+import com.android.internal.logging.UiEventLogger
+
+enum class StylusUiEvent(private val _id: Int) : UiEventLogger.UiEventEnum {
+    @UiEvent(doc = "UiEvent for USI low battery notification shown")
+    STYLUS_LOW_BATTERY_NOTIFICATION_SHOWN(1298),
+    @UiEvent(doc = "UiEvent for USI low battery notification clicked")
+    STYLUS_LOW_BATTERY_NOTIFICATION_CLICKED(1299),
+    @UiEvent(doc = "UiEvent for USI low battery notification dismissed")
+    STYLUS_LOW_BATTERY_NOTIFICATION_DISMISSED(1300),
+    @UiEvent(doc = "UIEvent for Toast shown when stylus started charging")
+    STYLUS_STARTED_CHARGING(1302),
+    @UiEvent(doc = "UIEvent for Toast shown when stylus stopped charging")
+    STYLUS_STOPPED_CHARGING(1303),
+    @UiEvent(doc = "UIEvent for bluetooth stylus connected") BLUETOOTH_STYLUS_CONNECTED(1304),
+    @UiEvent(doc = "UIEvent for bluetooth stylus disconnected") BLUETOOTH_STYLUS_DISCONNECTED(1305);
+
+    override fun getId() = _id
+}
diff --git a/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt b/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt
index 9050dad..89453ad 100644
--- a/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt
+++ b/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.stylus
 
 import android.Manifest
+import android.app.ActivityManager
 import android.app.PendingIntent
 import android.content.ActivityNotFoundException
 import android.content.BroadcastReceiver
@@ -32,6 +33,7 @@
 import androidx.core.app.NotificationCompat
 import androidx.core.app.NotificationManagerCompat
 import com.android.internal.annotations.VisibleForTesting
+import com.android.internal.logging.UiEventLogger
 import com.android.systemui.R
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
@@ -53,6 +55,7 @@
     private val notificationManager: NotificationManagerCompat,
     private val inputManager: InputManager,
     @Background private val handler: Handler,
+    private val uiEventLogger: UiEventLogger,
 ) {
 
     // These values must only be accessed on the handler.
@@ -79,12 +82,13 @@
 
     fun refresh() {
         handler.post refreshNotification@{
-            if (!suppressed && !hasConnectedBluetoothStylus() && isBatteryBelowThreshold()) {
+            val batteryBelowThreshold = isBatteryBelowThreshold()
+            if (!suppressed && !hasConnectedBluetoothStylus() && batteryBelowThreshold) {
                 showOrUpdateNotification()
                 return@refreshNotification
             }
 
-            if (!isBatteryBelowThreshold()) {
+            if (!batteryBelowThreshold) {
                 // Reset suppression when stylus battery is recharged, so that the next time
                 // it reaches a low battery, the notification will show again.
                 suppressed = false
@@ -143,6 +147,7 @@
                 .setAutoCancel(true)
                 .build()
 
+        logUiEvent(StylusUiEvent.STYLUS_LOW_BATTERY_NOTIFICATION_SHOWN)
         notificationManager.notify(USI_NOTIFICATION_ID, notification)
     }
 
@@ -168,8 +173,12 @@
         object : BroadcastReceiver() {
             override fun onReceive(context: Context, intent: Intent) {
                 when (intent.action) {
-                    ACTION_DISMISSED_LOW_BATTERY -> updateSuppression(true)
+                    ACTION_DISMISSED_LOW_BATTERY -> {
+                        logUiEvent(StylusUiEvent.STYLUS_LOW_BATTERY_NOTIFICATION_DISMISSED)
+                        updateSuppression(true)
+                    }
                     ACTION_CLICKED_LOW_BATTERY -> {
+                        logUiEvent(StylusUiEvent.STYLUS_LOW_BATTERY_NOTIFICATION_CLICKED)
                         updateSuppression(true)
                         if (inputDeviceId == null) return
 
@@ -195,6 +204,15 @@
             }
         }
 
+    private fun logUiEvent(metricId: StylusUiEvent) {
+        uiEventLogger.logWithPosition(
+            metricId,
+            ActivityManager.getCurrentUser(),
+            context.packageName,
+            (batteryCapacity * 100.0).toInt()
+        )
+    }
+
     companion object {
         // Low battery threshold matches CrOS, see:
         // https://source.chromium.org/chromium/chromium/src/+/main:ash/system/power/peripheral_battery_notifier.cc;l=41
@@ -203,10 +221,14 @@
         private val USI_NOTIFICATION_ID = R.string.stylus_battery_low_percentage
 
         @VisibleForTesting const val ACTION_DISMISSED_LOW_BATTERY = "StylusUsiPowerUI.dismiss"
+
         @VisibleForTesting const val ACTION_CLICKED_LOW_BATTERY = "StylusUsiPowerUI.click"
+
         @VisibleForTesting
         const val ACTION_STYLUS_USI_DETAILS = "com.android.settings.STYLUS_USI_DETAILS_SETTINGS"
+
         @VisibleForTesting const val KEY_DEVICE_INPUT_ID = "device_input_id"
+
         @VisibleForTesting const val KEY_SETTINGS_FRAGMENT_ARGS = ":settings:show_fragment_args"
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/theme/DynamicColors.kt b/packages/SystemUI/src/com/android/systemui/theme/DynamicColors.kt
new file mode 100644
index 0000000..c1f0c58
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/theme/DynamicColors.kt
@@ -0,0 +1,90 @@
+/*
+ * 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.theme
+
+import android.util.Pair
+import com.android.systemui.monet.dynamiccolor.DynamicColor
+import com.android.systemui.monet.dynamiccolor.MaterialDynamicColors as MDC
+
+class DynamicColors {
+    companion object {
+        @JvmField
+        val ALL_DYNAMIC_COLORS_MAPPED: List<Pair<String, DynamicColor>> =
+            arrayListOf(
+                Pair.create("primary_container", MDC.primaryContainer),
+                Pair.create("on_primary_container", MDC.onPrimaryContainer),
+                Pair.create("primary", MDC.primary),
+                Pair.create("on_primary", MDC.onPrimary),
+                Pair.create("secondary_container", MDC.secondaryContainer),
+                Pair.create("on_secondary_container", MDC.onSecondaryContainer),
+                Pair.create("secondary", MDC.secondary),
+                Pair.create("on_secondary", MDC.onSecondary),
+                Pair.create("tertiary_container", MDC.tertiaryContainer),
+                Pair.create("on_tertiary_container", MDC.onTertiaryContainer),
+                Pair.create("tertiary", MDC.tertiary),
+                Pair.create("on_tertiary", MDC.onTertiary),
+                Pair.create("background", MDC.background),
+                Pair.create("on_background", MDC.onBackground),
+                Pair.create("surface", MDC.surface),
+                Pair.create("on_surface", MDC.onSurface),
+                Pair.create("surface_container_low", MDC.surfaceContainerLow),
+                Pair.create("surface_container_lowest", MDC.surfaceContainerLowest),
+                Pair.create("surface_container", MDC.surfaceContainer),
+                Pair.create("surface_container_high", MDC.surfaceContainerHigh),
+                Pair.create("surface_container_highest", MDC.surfaceContainerHighest),
+                Pair.create("surface_bright", MDC.surfaceBright),
+                Pair.create("surface_dim", MDC.surfaceDim),
+                Pair.create("surface_variant", MDC.surfaceVariant),
+                Pair.create("on_surface_variant", MDC.onSurfaceVariant),
+                Pair.create("outline", MDC.outline),
+                Pair.create("error", MDC.error),
+                Pair.create("on_error", MDC.onError),
+                Pair.create("error_container", MDC.errorContainer),
+                Pair.create("on_error_container", MDC.onErrorContainer),
+                Pair.create("primary_fixed", MDC.primaryFixed),
+                Pair.create("primary_fixed_darker", MDC.primaryFixedDarker),
+                Pair.create("on_primary_fixed", MDC.onPrimaryFixed),
+                Pair.create("on_primary_fixed_variant", MDC.onPrimaryFixedVariant),
+                Pair.create("secondary_fixed", MDC.secondaryFixed),
+                Pair.create("secondary_fixed_darker", MDC.secondaryFixedDarker),
+                Pair.create("on_secondary_fixed", MDC.onSecondaryFixed),
+                Pair.create("on_secondary_fixed_variant", MDC.onSecondaryFixedVariant),
+                Pair.create("tertiary_fixed", MDC.tertiaryFixed),
+                Pair.create("tertiary_fixed_darker", MDC.tertiaryFixedDarker),
+                Pair.create("on_tertiary_fixed", MDC.onTertiaryFixed),
+                Pair.create("on_tertiary_fixed_variant", MDC.onTertiaryFixedVariant),
+                Pair.create("control_activated", MDC.controlActivated),
+                Pair.create("control_normal", MDC.controlNormal),
+                Pair.create("control_highlight", MDC.controlHighlight),
+                Pair.create("text_primary_inverse", MDC.textPrimaryInverse),
+                Pair.create(
+                    "text_secondary_and_tertiary_inverse",
+                    MDC.textSecondaryAndTertiaryInverse
+                ),
+                Pair.create("text_primary_inverse_disable_only", MDC.textPrimaryInverseDisableOnly),
+                Pair.create(
+                    "text_secondary_and_tertiary_inverse_disabled",
+                    MDC.textSecondaryAndTertiaryInverseDisabled
+                ),
+                Pair.create("text_hint_inverse", MDC.textHintInverse),
+                Pair.create("palette_key_color_primary", MDC.primaryPaletteKeyColor),
+                Pair.create("palette_key_color_secondary", MDC.secondaryPaletteKeyColor),
+                Pair.create("palette_key_color_tertiary", MDC.tertiaryPaletteKeyColor),
+                Pair.create("palette_key_color_neutral", MDC.neutralPaletteKeyColor),
+                Pair.create("palette_key_color_neutral_variant", MDC.neutralVariantPaletteKeyColor)
+            )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
index ba39367..3376e23 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
@@ -65,6 +65,8 @@
     @VisibleForTesting
     static final String SYSUI_PACKAGE = "com.android.systemui";
 
+    static final String OVERLAY_CATEGORY_DYNAMIC_COLOR =
+            "android.theme.customization.dynamic_color";
     static final String OVERLAY_CATEGORY_ACCENT_COLOR =
             "android.theme.customization.accent_color";
     static final String OVERLAY_CATEGORY_SYSTEM_PALETTE =
@@ -117,6 +119,7 @@
             OVERLAY_CATEGORY_SHAPE,
             OVERLAY_CATEGORY_FONT,
             OVERLAY_CATEGORY_ACCENT_COLOR,
+            OVERLAY_CATEGORY_DYNAMIC_COLOR,
             OVERLAY_CATEGORY_ICON_ANDROID,
             OVERLAY_CATEGORY_ICON_SYSUI,
             OVERLAY_CATEGORY_ICON_SETTINGS,
@@ -127,6 +130,7 @@
     static final Set<String> SYSTEM_USER_CATEGORIES = Sets.newHashSet(
             OVERLAY_CATEGORY_SYSTEM_PALETTE,
             OVERLAY_CATEGORY_ACCENT_COLOR,
+            OVERLAY_CATEGORY_DYNAMIC_COLOR,
             OVERLAY_CATEGORY_FONT,
             OVERLAY_CATEGORY_SHAPE,
             OVERLAY_CATEGORY_ICON_ANDROID,
@@ -153,6 +157,7 @@
         mThemePickerPackage = themePickerPackage;
         mTargetPackageToCategories.put(ANDROID_PACKAGE, Sets.newHashSet(
                 OVERLAY_CATEGORY_SYSTEM_PALETTE, OVERLAY_CATEGORY_ACCENT_COLOR,
+                OVERLAY_CATEGORY_DYNAMIC_COLOR,
                 OVERLAY_CATEGORY_FONT, OVERLAY_CATEGORY_SHAPE,
                 OVERLAY_CATEGORY_ICON_ANDROID));
         mTargetPackageToCategories.put(SYSUI_PACKAGE,
@@ -164,6 +169,7 @@
         mTargetPackageToCategories.put(mThemePickerPackage,
                 Sets.newHashSet(OVERLAY_CATEGORY_ICON_THEME_PICKER));
         mCategoryToTargetPackage.put(OVERLAY_CATEGORY_ACCENT_COLOR, ANDROID_PACKAGE);
+        mCategoryToTargetPackage.put(OVERLAY_CATEGORY_DYNAMIC_COLOR, ANDROID_PACKAGE);
         mCategoryToTargetPackage.put(OVERLAY_CATEGORY_FONT, ANDROID_PACKAGE);
         mCategoryToTargetPackage.put(OVERLAY_CATEGORY_SHAPE, ANDROID_PACKAGE);
         mCategoryToTargetPackage.put(OVERLAY_CATEGORY_ICON_ANDROID, ANDROID_PACKAGE);
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 6b507ba..2ad3558 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -15,11 +15,14 @@
  */
 package com.android.systemui.theme;
 
+import static android.util.TypedValue.TYPE_INT_COLOR_ARGB8;
+
 import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP;
 import static com.android.systemui.theme.ThemeOverlayApplier.COLOR_SOURCE_HOME;
 import static com.android.systemui.theme.ThemeOverlayApplier.COLOR_SOURCE_LOCK;
 import static com.android.systemui.theme.ThemeOverlayApplier.COLOR_SOURCE_PRESET;
 import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ACCENT_COLOR;
+import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_DYNAMIC_COLOR;
 import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_SYSTEM_PALETTE;
 import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_COLOR_BOTH;
 import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_COLOR_INDEX;
@@ -51,7 +54,7 @@
 import android.util.Log;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
-import android.util.TypedValue;
+import android.view.accessibility.AccessibilityManager;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
@@ -70,6 +73,16 @@
 import com.android.systemui.monet.ColorScheme;
 import com.android.systemui.monet.Style;
 import com.android.systemui.monet.TonalPalette;
+import com.android.systemui.monet.dynamiccolor.MaterialDynamicColors;
+import com.android.systemui.monet.hct.Hct;
+import com.android.systemui.monet.scheme.DynamicScheme;
+import com.android.systemui.monet.scheme.SchemeExpressive;
+import com.android.systemui.monet.scheme.SchemeFruitSalad;
+import com.android.systemui.monet.scheme.SchemeMonochrome;
+import com.android.systemui.monet.scheme.SchemeNeutral;
+import com.android.systemui.monet.scheme.SchemeRainbow;
+import com.android.systemui.monet.scheme.SchemeTonalSpot;
+import com.android.systemui.monet.scheme.SchemeVibrant;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
@@ -105,9 +118,6 @@
     protected static final String TAG = "ThemeOverlayController";
     private static final boolean DEBUG = true;
 
-    protected static final int NEUTRAL = 0;
-    protected static final int ACCENT = 1;
-
     private final ThemeOverlayApplier mThemeManager;
     private final UserManager mUserManager;
     private final BroadcastDispatcher mBroadcastDispatcher;
@@ -130,12 +140,17 @@
     private boolean mNeedsOverlayCreation;
     // Dominant color extracted from wallpaper, NOT the color used on the overlay
     protected int mMainWallpaperColor = Color.TRANSPARENT;
+    // UI contrast as reported by AccessibilityManager
+    private float mUiContrast = 0;
     // Theme variant: Vibrant, Tonal, Expressive, etc
-    private Style mThemeStyle = Style.TONAL_SPOT;
+    @VisibleForTesting
+    protected Style mThemeStyle = Style.TONAL_SPOT;
     // Accent colors overlay
     private FabricatedOverlay mSecondaryOverlay;
     // Neutral system colors overlay
     private FabricatedOverlay mNeutralOverlay;
+    // Dynamic colors overlay
+    private FabricatedOverlay mDynamicOverlay;
     // If wallpaper color event will be accepted and change the UI colors.
     private boolean mAcceptColorEvents = true;
     // If non-null (per user), colors that were sent to the framework, and processing was deferred
@@ -143,6 +158,9 @@
     private final SparseArray<WallpaperColors> mDeferredWallpaperColors = new SparseArray<>();
     private final SparseIntArray mDeferredWallpaperColorsFlags = new SparseIntArray();
     private final WakefulnessLifecycle mWakefulnessLifecycle;
+    private final AccessibilityManager mAccessibilityManager;
+    private DynamicScheme mDynamicSchemeDark;
+    private DynamicScheme mDynamicSchemeLight;
 
     // Defers changing themes until Setup Wizard is done.
     private boolean mDeferredThemeEvaluation;
@@ -304,6 +322,7 @@
                 mSkipSettingChange = true;
                 if (jsonObject.has(OVERLAY_CATEGORY_ACCENT_COLOR) || jsonObject.has(
                         OVERLAY_CATEGORY_SYSTEM_PALETTE)) {
+                    jsonObject.remove(OVERLAY_CATEGORY_DYNAMIC_COLOR);
                     jsonObject.remove(OVERLAY_CATEGORY_ACCENT_COLOR);
                     jsonObject.remove(OVERLAY_CATEGORY_SYSTEM_PALETTE);
                     jsonObject.remove(OVERLAY_COLOR_INDEX);
@@ -372,7 +391,8 @@
             DumpManager dumpManager,
             FeatureFlags featureFlags,
             @Main Resources resources,
-            WakefulnessLifecycle wakefulnessLifecycle) {
+            WakefulnessLifecycle wakefulnessLifecycle,
+            AccessibilityManager accessibilityManager) {
         mContext = context;
         mIsMonochromaticEnabled = featureFlags.isEnabled(Flags.MONOCHROMATIC_THEME);
         mIsMonetEnabled = featureFlags.isEnabled(Flags.MONET);
@@ -388,6 +408,7 @@
         mUserTracker = userTracker;
         mResources = resources;
         mWakefulnessLifecycle = wakefulnessLifecycle;
+        mAccessibilityManager = accessibilityManager;
         dumpManager.registerDumpable(TAG, this);
     }
 
@@ -424,6 +445,12 @@
                     }
                 },
                 UserHandle.USER_ALL);
+        mUiContrast = mAccessibilityManager.getUiContrast();
+        mAccessibilityManager.addUiContrastChangeListener(mMainExecutor, uiContrast -> {
+            mUiContrast = uiContrast;
+            // Force reload so that we update even when the main color has not changed
+            reevaluateSystemTheme(true /* forceReload */);
+        });
 
         if (!mIsMonetEnabled) {
             return;
@@ -496,12 +523,11 @@
 
         if (mIsMonetEnabled) {
             mThemeStyle = fetchThemeStyleFromSetting();
-            mSecondaryOverlay = getOverlay(mMainWallpaperColor, ACCENT, mThemeStyle);
-            mNeutralOverlay = getOverlay(mMainWallpaperColor, NEUTRAL, mThemeStyle);
+            createOverlays(mMainWallpaperColor);
             mNeedsOverlayCreation = true;
             if (DEBUG) {
                 Log.d(TAG, "fetched overlays. accent: " + mSecondaryOverlay
-                        + " neutral: " + mNeutralOverlay);
+                        + " neutral: " + mNeutralOverlay + " dynamic: " + mDynamicOverlay);
             }
         }
 
@@ -519,42 +545,95 @@
         return ColorScheme.getSeedColor(wallpaperColors);
     }
 
-    /**
-     * Given a color candidate, return an overlay definition.
-     */
-    protected FabricatedOverlay getOverlay(int color, int type, Style style) {
-        boolean nightMode = (mResources.getConfiguration().uiMode
-                & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES;
-
-        mColorScheme = new ColorScheme(color, nightMode, style);
-        String name = type == ACCENT ? "accent" : "neutral";
-
-        FabricatedOverlay.Builder overlay =
-                new FabricatedOverlay.Builder("com.android.systemui", name, "android");
-
-        if (type == ACCENT) {
-            assignTonalPaletteToOverlay("accent1", overlay, mColorScheme.getAccent1());
-            assignTonalPaletteToOverlay("accent2", overlay, mColorScheme.getAccent2());
-            assignTonalPaletteToOverlay("accent3", overlay, mColorScheme.getAccent3());
-        } else {
-            assignTonalPaletteToOverlay("neutral1", overlay, mColorScheme.getNeutral1());
-            assignTonalPaletteToOverlay("neutral2", overlay, mColorScheme.getNeutral2());
+    private static DynamicScheme dynamicSchemeFromStyle(Style style, int color,
+            boolean isDark, double contrastLevel) {
+        Hct sourceColorHct = Hct.fromInt(color);
+        switch (style) {
+            case EXPRESSIVE:
+                return new SchemeExpressive(sourceColorHct, isDark, contrastLevel);
+            case SPRITZ:
+                return new SchemeNeutral(sourceColorHct, isDark, contrastLevel);
+            case TONAL_SPOT:
+                return new SchemeTonalSpot(sourceColorHct, isDark, contrastLevel);
+            case FRUIT_SALAD:
+                return new SchemeFruitSalad(sourceColorHct, isDark, contrastLevel);
+            case RAINBOW:
+                return new SchemeRainbow(sourceColorHct, isDark, contrastLevel);
+            case VIBRANT:
+                return new SchemeVibrant(sourceColorHct, isDark, contrastLevel);
+            case MONOCHROMATIC:
+                return new SchemeMonochrome(sourceColorHct, isDark, contrastLevel);
+            default:
+                return null;
         }
-
-        return overlay.build();
     }
 
-    private void assignTonalPaletteToOverlay(String name,
-            FabricatedOverlay.Builder overlay, TonalPalette tonalPalette) {
+    @VisibleForTesting
+    protected boolean isNightMode() {
+        return (mResources.getConfiguration().uiMode
+                & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES;
+    }
 
+    @VisibleForTesting
+    protected FabricatedOverlay newFabricatedOverlay(String name) {
+        return new FabricatedOverlay.Builder("com.android.systemui", name, "android").build();
+    }
+
+    private void createOverlays(int color) {
+        boolean nightMode = isNightMode();
+        mColorScheme = new ColorScheme(color, nightMode, mThemeStyle);
+        mNeutralOverlay = createNeutralOverlay();
+        mSecondaryOverlay = createAccentOverlay();
+
+        mDynamicSchemeDark = dynamicSchemeFromStyle(
+                mThemeStyle, color, true /* isDark */, mUiContrast);
+        mDynamicSchemeLight = dynamicSchemeFromStyle(
+                mThemeStyle, color, false /* isDark */, mUiContrast);
+        mDynamicOverlay = createDynamicOverlay();
+    }
+
+    protected FabricatedOverlay createNeutralOverlay() {
+        FabricatedOverlay overlay = newFabricatedOverlay("neutral");
+        assignTonalPaletteToOverlay("neutral1", overlay, mColorScheme.getNeutral1());
+        assignTonalPaletteToOverlay("neutral2", overlay, mColorScheme.getNeutral2());
+        return overlay;
+    }
+
+    protected FabricatedOverlay createAccentOverlay() {
+        FabricatedOverlay overlay = newFabricatedOverlay("accent");
+        assignTonalPaletteToOverlay("accent1", overlay, mColorScheme.getAccent1());
+        assignTonalPaletteToOverlay("accent2", overlay, mColorScheme.getAccent2());
+        assignTonalPaletteToOverlay("accent3", overlay, mColorScheme.getAccent3());
+        return overlay;
+    }
+
+    private void assignTonalPaletteToOverlay(String name, FabricatedOverlay overlay,
+            TonalPalette tonalPalette) {
         String resourcePrefix = "android:color/system_" + name;
-        int colorDataType = TypedValue.TYPE_INT_COLOR_ARGB8;
 
         tonalPalette.getAllShadesMapped().forEach((key, value) -> {
             String resourceName = resourcePrefix + "_" + key;
             int colorValue = ColorUtils.setAlphaComponent(value, 0xFF);
-            overlay.setResourceValue(resourceName, colorDataType,
-                    colorValue);
+            overlay.setResourceValue(resourceName, TYPE_INT_COLOR_ARGB8, colorValue,
+                    null /* configuration */);
+        });
+    }
+
+    protected FabricatedOverlay createDynamicOverlay() {
+        FabricatedOverlay overlay = newFabricatedOverlay("dynamic");
+        assignDynamicPaletteToOverlay(overlay, true /* isDark */);
+        assignDynamicPaletteToOverlay(overlay, false /* isDark */);
+        return overlay;
+    }
+
+    private void assignDynamicPaletteToOverlay(FabricatedOverlay overlay, boolean isDark) {
+        String suffix = isDark ? "dark" : "light";
+        DynamicScheme scheme = isDark ? mDynamicSchemeDark : mDynamicSchemeLight;
+        DynamicColors.ALL_DYNAMIC_COLORS_MAPPED.forEach(p -> {
+            String resourceName = "android:color/system_" + p.first + "_" + suffix;
+            int colorValue = p.second.getArgb(scheme);
+            overlay.setResourceValue(resourceName, TYPE_INT_COLOR_ARGB8, colorValue,
+                    null /* configuration */);
         });
     }
 
@@ -568,16 +647,21 @@
         for (UserHandle userHandle : allProfiles) {
             Resources res = userHandle.isSystem()
                     ? mResources : mContext.createContextAsUser(userHandle, 0).getResources();
-            if (!(res.getColor(android.R.color.system_accent1_500, mContext.getTheme())
+            Resources.Theme theme = mContext.getTheme();
+            if (!(res.getColor(android.R.color.system_accent1_500, theme)
                     == mColorScheme.getAccent1().getS500()
-                    && res.getColor(android.R.color.system_accent2_500, mContext.getTheme())
+                    && res.getColor(android.R.color.system_accent2_500, theme)
                     == mColorScheme.getAccent2().getS500()
-                    && res.getColor(android.R.color.system_accent3_500, mContext.getTheme())
+                    && res.getColor(android.R.color.system_accent3_500, theme)
                     == mColorScheme.getAccent3().getS500()
-                    && res.getColor(android.R.color.system_neutral1_500, mContext.getTheme())
+                    && res.getColor(android.R.color.system_neutral1_500, theme)
                     == mColorScheme.getNeutral1().getS500()
-                    && res.getColor(android.R.color.system_neutral2_500, mContext.getTheme())
-                    == mColorScheme.getNeutral2().getS500())) {
+                    && res.getColor(android.R.color.system_neutral2_500, theme)
+                    == mColorScheme.getNeutral2().getS500()
+                    && res.getColor(android.R.color.system_primary_container_dark, theme)
+                    == MaterialDynamicColors.primaryContainer.getArgb(mDynamicSchemeDark)
+                    && res.getColor(android.R.color.system_primary_container_light, theme)
+                    == MaterialDynamicColors.primaryContainer.getArgb(mDynamicSchemeLight))) {
                 return false;
             }
         }
@@ -614,12 +698,11 @@
                 if (!colorString.startsWith("#")) {
                     colorString = "#" + colorString;
                 }
-                int color = Color.parseColor(colorString);
-                mNeutralOverlay = getOverlay(color, NEUTRAL, mThemeStyle);
-                mSecondaryOverlay = getOverlay(color, ACCENT, mThemeStyle);
+                createOverlays(Color.parseColor(colorString));
                 mNeedsOverlayCreation = true;
                 categoryToPackage.remove(OVERLAY_CATEGORY_SYSTEM_PALETTE);
                 categoryToPackage.remove(OVERLAY_CATEGORY_ACCENT_COLOR);
+                categoryToPackage.remove(OVERLAY_CATEGORY_DYNAMIC_COLOR);
             } catch (Exception e) {
                 // Color.parseColor doesn't catch any exceptions from the calls it makes
                 Log.w(TAG, "Invalid color definition: " + systemPalette.getPackageName(), e);
@@ -631,6 +714,7 @@
                 // fail.
                 categoryToPackage.remove(OVERLAY_CATEGORY_SYSTEM_PALETTE);
                 categoryToPackage.remove(OVERLAY_CATEGORY_ACCENT_COLOR);
+                categoryToPackage.remove(OVERLAY_CATEGORY_DYNAMIC_COLOR);
             } catch (NumberFormatException e) {
                 // This is a package name. All good, let's continue
             }
@@ -647,6 +731,10 @@
                 && mSecondaryOverlay != null) {
             categoryToPackage.put(OVERLAY_CATEGORY_ACCENT_COLOR, mSecondaryOverlay.getIdentifier());
         }
+        if (!categoryToPackage.containsKey(OVERLAY_CATEGORY_DYNAMIC_COLOR)
+                && mDynamicOverlay != null) {
+            categoryToPackage.put(OVERLAY_CATEGORY_DYNAMIC_COLOR, mDynamicOverlay.getIdentifier());
+        }
 
         Set<UserHandle> managedProfiles = new HashSet<>();
         for (UserInfo userInfo : mUserManager.getEnabledProfiles(currentUser)) {
@@ -668,7 +756,7 @@
         if (mNeedsOverlayCreation) {
             mNeedsOverlayCreation = false;
             mThemeManager.applyCurrentUserOverlays(categoryToPackage, new FabricatedOverlay[]{
-                    mSecondaryOverlay, mNeutralOverlay
+                    mSecondaryOverlay, mNeutralOverlay, mDynamicOverlay
             }, currentUser, managedProfiles);
         } else {
             mThemeManager.applyCurrentUserOverlays(categoryToPackage, null, currentUser,
@@ -710,6 +798,7 @@
         pw.println("mMainWallpaperColor=" + Integer.toHexString(mMainWallpaperColor));
         pw.println("mSecondaryOverlay=" + mSecondaryOverlay);
         pw.println("mNeutralOverlay=" + mNeutralOverlay);
+        pw.println("mDynamicOverlay=" + mDynamicOverlay);
         pw.println("mIsMonetEnabled=" + mIsMonetEnabled);
         pw.println("mColorScheme=" + mColorScheme);
         pw.println("mNeedsOverlayCreation=" + mNeedsOverlayCreation);
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
index 5cf01af..70523bb 100644
--- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
@@ -47,12 +47,14 @@
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.withContext
@@ -123,9 +125,25 @@
     featureFlags: FeatureFlags,
 ) : UserRepository {
 
-    private val _userSwitcherSettings = MutableStateFlow(runBlocking { getSettings() })
-    override val userSwitcherSettings: Flow<UserSwitcherSettingsModel> =
-        _userSwitcherSettings.asStateFlow().filterNotNull()
+    private val _userSwitcherSettings: StateFlow<UserSwitcherSettingsModel> =
+        globalSettings
+            .observerFlow(
+                names =
+                    arrayOf(
+                        SETTING_SIMPLE_USER_SWITCHER,
+                        Settings.Global.ADD_USERS_WHEN_LOCKED,
+                        Settings.Global.USER_SWITCHER_ENABLED,
+                    ),
+                userId = UserHandle.USER_SYSTEM,
+            )
+            .onStart { emit(Unit) } // Forces an initial update.
+            .map { getSettings() }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.Eagerly,
+                initialValue = runBlocking { getSettings() },
+            )
+    override val userSwitcherSettings: Flow<UserSwitcherSettingsModel> = _userSwitcherSettings
 
     private val _userInfos = MutableStateFlow<List<UserInfo>?>(null)
     override val userInfos: Flow<List<UserInfo>> = _userInfos.filterNotNull()
@@ -159,7 +177,6 @@
 
     init {
         observeSelectedUser()
-        observeUserSettings()
         if (featureFlags.isEnabled(FACE_AUTH_REFACTOR)) {
             observeUserSwitching()
         }
@@ -247,23 +264,6 @@
             .launchIn(applicationScope)
     }
 
-    private fun observeUserSettings() {
-        globalSettings
-            .observerFlow(
-                names =
-                    arrayOf(
-                        SETTING_SIMPLE_USER_SWITCHER,
-                        Settings.Global.ADD_USERS_WHEN_LOCKED,
-                        Settings.Global.USER_SWITCHER_ENABLED,
-                    ),
-                userId = UserHandle.USER_SYSTEM,
-            )
-            .onStart { emit(Unit) } // Forces an initial update.
-            .map { getSettings() }
-            .onEach { _userSwitcherSettings.value = it }
-            .launchIn(applicationScope)
-    }
-
     private suspend fun getSettings(): UserSwitcherSettingsModel {
         return withContext(backgroundDispatcher) {
             val isSimpleUserSwitcher =
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 0f9ae39..559423b 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -1244,13 +1244,13 @@
             rescheduleTimeoutH();
         }
 
-        if (mODICaptionsTooltipView != null) {
-            mODICaptionsTooltipView.setAlpha(0.0f);
+        // We need to wait for layout and then center the caption view. Since the height of the
+        // dialog is now dynamic (with the variable ringer drawer height changing the height of
+        // the dialog), we need to do this here in code vs. in XML.
+        mHandler.post(() -> {
+            if (mODICaptionsTooltipView != null) {
+                mODICaptionsTooltipView.setAlpha(0.0f);
 
-            // We need to wait for layout and then center the caption view. Since the height of the
-            // dialog is now dynamic (with the variable ringer drawer height changing the height of
-            // the dialog), we need to do this here in code vs. in XML.
-            mHandler.post(() -> {
                 final int[] odiTooltipLocation = mODICaptionsTooltipView.getLocationOnScreen();
                 final int[] odiButtonLocation = mODICaptionsIcon.getLocationOnScreen();
 
@@ -1276,8 +1276,8 @@
                             }
                         })
                         .start();
-            });
-        }
+            }
+        });
     }
 
     private void hideCaptionsTooltip() {
diff --git a/packages/SystemUI/tests/res/layout/invalid_notification_height.xml b/packages/SystemUI/tests/res/layout/invalid_notification_height.xml
new file mode 100644
index 0000000..aac43bf
--- /dev/null
+++ b/packages/SystemUI/tests/res/layout/invalid_notification_height.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<View xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="5dp"/>
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index ed9b5cf..4110b5a 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -28,6 +28,7 @@
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
 import static com.android.keyguard.FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED;
+import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_AVAILABLE;
 import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_STATE_CANCELLING_RESTARTING;
 import static com.android.keyguard.KeyguardUpdateMonitor.DEFAULT_CANCEL_SIGNAL_TIMEOUT;
 import static com.android.keyguard.KeyguardUpdateMonitor.HAL_POWER_PRESS_TIMEOUT;
@@ -241,7 +242,7 @@
     @Mock
     private FingerprintInteractiveToAuthProvider mInteractiveToAuthProvider;
 
-
+    private List<FingerprintSensorPropertiesInternal> mFingerprintSensorProperties;
     private final int mCurrentUserId = 100;
     private final UserInfo mCurrentUserInfo = new UserInfo(mCurrentUserId, "Test user", 0);
 
@@ -280,9 +281,7 @@
         when(mFaceSensorProperties.get(anyInt())).thenReturn(
                 createFaceSensorProperties(/* supportsFaceDetection = */ false));
 
-        when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
-        when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
-        when(mFingerprintManager.getSensorPropertiesInternal()).thenReturn(List.of(
+        mFingerprintSensorProperties = List.of(
                 new FingerprintSensorPropertiesInternal(1 /* sensorId */,
                         FingerprintSensorProperties.STRENGTH_STRONG,
                         1 /* maxEnrollmentsPerUser */,
@@ -291,7 +290,11 @@
                                 "1.01" /* firmwareVersion */,
                                 "00000001" /* serialNumber */, "" /* softwareVersion */)),
                         FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
-                        false /* resetLockoutRequiresHAT */)));
+                        false /* resetLockoutRequiresHAT */));
+        when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
+        when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
+        when(mFingerprintManager.getSensorPropertiesInternal()).thenReturn(
+                mFingerprintSensorProperties);
         when(mUserManager.isUserUnlocked(anyInt())).thenReturn(true);
         when(mUserManager.isPrimaryUser()).thenReturn(true);
         when(mStrongAuthTracker.getStub()).thenReturn(mock(IStrongAuthTracker.Stub.class));
@@ -769,12 +772,43 @@
     }
 
     @Test
+    public void nofaceDetect_whenStrongAuthRequiredAndBypassUdfpsSupportedAndFpRunning() {
+        // GIVEN mocked keyguardUpdateMonitorCallback
+        KeyguardUpdateMonitorCallback keyguardUpdateMonitorCallback =
+                mock(KeyguardUpdateMonitorCallback.class);
+        mKeyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback);
+
+        // GIVEN bypass is enabled, face detection is supported
+        lockscreenBypassIsAllowed();
+        supportsFaceDetection();
+        keyguardIsVisible();
+
+        // GIVEN udfps is supported and strong auth required for weak biometrics (face) only
+        givenUdfpsSupported();
+        strongAuthRequiredForWeakBiometricOnly(); // this allows fingerprint to run but not face
+
+        // WHEN the device wakes up
+        mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
+        mTestableLooper.processAllMessages();
+
+        // THEN face detect and authenticate are NOT triggered
+        verify(mFaceManager, never()).detectFace(any(), any(), anyInt());
+        verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
+                anyBoolean());
+
+        // THEN biometric help message sent to callback
+        verify(keyguardUpdateMonitorCallback).onBiometricHelp(
+                eq(BIOMETRIC_HELP_FACE_NOT_AVAILABLE), anyString(), eq(BiometricSourceType.FACE));
+    }
+
+    @Test
     public void faceDetect_whenStrongAuthRequiredAndBypass() {
         // 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
 
         // WHEN the device wakes up
         mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
@@ -1732,7 +1766,7 @@
     }
 
     @Test
-    public void testShouldListenForFace_whenOccludingAppRequestsFaceAuth_returnsTrue()
+    public void shouldListenForFace_secureCameraLaunchedButAlternateBouncerIsLaunched_returnsTrue()
             throws RemoteException {
         // Face auth should run when the following is true.
         keyguardNotGoingAway();
@@ -1748,7 +1782,7 @@
 
         assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
 
-        occludingAppRequestsFaceAuth();
+        alternateBouncerVisible();
         mTestableLooper.processAllMessages();
 
         assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
@@ -1838,7 +1872,7 @@
     }
 
     @Test
-    public void testShouldListenForFace_whenUdfpsBouncerIsShowing_returnsTrue()
+    public void testShouldListenForFace_whenAlternateBouncerIsShowing_returnsTrue()
             throws RemoteException {
         // Preconditions for face auth to run
         keyguardNotGoingAway();
@@ -1850,13 +1884,13 @@
         mTestableLooper.processAllMessages();
         assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
 
-        mKeyguardUpdateMonitor.setUdfpsBouncerShowing(true);
+        mKeyguardUpdateMonitor.setAlternateBouncerShowing(true);
 
         assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
     }
 
     @Test
-    public void testShouldListenForFace_udfpsBouncerIsShowingButDeviceGoingToSleep_returnsFalse()
+    public void testShouldListenForFace_alternateBouncerShowingButDeviceGoingToSleep_returnsFalse()
             throws RemoteException {
         // Preconditions for face auth to run
         keyguardNotGoingAway();
@@ -1866,7 +1900,7 @@
         biometricsEnabledForCurrentUser();
         userNotCurrentlySwitching();
         deviceNotGoingToSleep();
-        mKeyguardUpdateMonitor.setUdfpsBouncerShowing(true);
+        alternateBouncerVisible();
         mTestableLooper.processAllMessages();
         assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
 
@@ -1876,6 +1910,10 @@
         assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
     }
 
+    private void alternateBouncerVisible() {
+        mKeyguardUpdateMonitor.setAlternateBouncerShowing(true);
+    }
+
     @Test
     public void testShouldListenForFace_whenFaceIsLockedOut_returnsTrue()
             throws RemoteException {
@@ -1886,7 +1924,7 @@
         biometricsNotDisabledThroughDevicePolicyManager();
         biometricsEnabledForCurrentUser();
         userNotCurrentlySwitching();
-        mKeyguardUpdateMonitor.setUdfpsBouncerShowing(true);
+        mKeyguardUpdateMonitor.setAlternateBouncerShowing(true);
         mTestableLooper.processAllMessages();
         assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
 
@@ -2465,6 +2503,11 @@
         when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
     }
 
+    private void strongAuthRequiredForWeakBiometricOnly() {
+        when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(eq(true))).thenReturn(true);
+        when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(eq(false))).thenReturn(false);
+    }
+
     private void strongAuthNotRequired() {
         when(mStrongAuthTracker.getStrongAuthForUser(KeyguardUpdateMonitor.getCurrentUser()))
                 .thenReturn(0);
@@ -2519,6 +2562,12 @@
         mTestableLooper.processAllMessages();
     }
 
+    private void givenUdfpsSupported() {
+        Assert.assertFalse(mFingerprintSensorProperties.isEmpty());
+        when(mAuthController.getUdfpsProps()).thenReturn(mFingerprintSensorProperties);
+        Assert.assertTrue(mKeyguardUpdateMonitor.isUdfpsSupported());
+    }
+
     private void setBroadcastReceiverPendingResult(BroadcastReceiver receiver) {
         BroadcastReceiver.PendingResult pendingResult =
                 new BroadcastReceiver.PendingResult(Activity.RESULT_OK,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index 17c262d..05266f1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -189,8 +189,6 @@
     @Mock
     private UdfpsView mUdfpsView;
     @Mock
-    private UdfpsEnrollView mEnrollView;
-    @Mock
     private UdfpsBpView mBpView;
     @Mock
     private UdfpsFpmEmptyView mFpmEmptyView;
@@ -241,15 +239,12 @@
 
         when(mLayoutInflater.inflate(R.layout.udfps_view, null, false))
                 .thenReturn(mUdfpsView);
-        when(mLayoutInflater.inflate(R.layout.udfps_enroll_view, null))
-                .thenReturn(mEnrollView); // for showOverlay REASON_ENROLL_ENROLLING
         when(mLayoutInflater.inflate(R.layout.udfps_keyguard_view, null))
                 .thenReturn(mKeyguardView); // for showOverlay REASON_AUTH_FPM_KEYGUARD
         when(mLayoutInflater.inflate(R.layout.udfps_bp_view, null))
                 .thenReturn(mBpView);
         when(mLayoutInflater.inflate(R.layout.udfps_fpm_empty_view, null))
                 .thenReturn(mFpmEmptyView);
-        when(mEnrollView.getContext()).thenReturn(mContext);
         when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
         when(mSessionTracker.getSessionId(anyInt())).thenReturn(
                 (new InstanceIdSequence(1 << 20)).newInstanceId());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt
index c40fd4fa..ec2c1bc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt
@@ -607,12 +607,16 @@
                     pointerCoords = pointerCoords
                 )
 
+            val expectedTouchDataPointer =
+                currentPointers.find { it.id == expectedPointerOnSensorId }
+                    ?: currentPointers.find { it.id == previousPointerOnSensorId }
+                        ?: currentPointers[0]
             val expectedTouchData =
-                if (expectedPointerOnSensorId != INVALID_POINTER_ID) {
+                if (motionEventAction != MotionEvent.ACTION_CANCEL) {
                     NORMALIZED_TOUCH_DATA.copy(
-                        pointerId = expectedPointerOnSensorId,
-                        x = ROTATION_0_INPUTS.getNativeX(isWithinSensor = true),
-                        y = ROTATION_0_INPUTS.getNativeY(isWithinSensor = true)
+                        pointerId = expectedTouchDataPointer.id,
+                        x = ROTATION_0_INPUTS.getNativeX(expectedTouchDataPointer.onSensor),
+                        y = ROTATION_0_INPUTS.getNativeY(expectedTouchDataPointer.onSensor)
                     )
                 } else {
                     NormalizedTouchData()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationTypesUpdaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationTypesUpdaterTest.java
index 9f4a7c8..b9db9c4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationTypesUpdaterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationTypesUpdaterTest.java
@@ -33,6 +33,7 @@
 import com.android.settingslib.dream.DreamBackend;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.shared.condition.Monitor;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.settings.SecureSettings;
 import com.android.systemui.util.time.FakeSystemClock;
@@ -66,13 +67,16 @@
 
     private ComplicationTypesUpdater mController;
 
+    @Mock
+    private Monitor mMonitor;
+
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         when(mDreamBackend.getEnabledComplications()).thenReturn(new HashSet<>());
 
         mController = new ComplicationTypesUpdater(mDreamBackend, mExecutor,
-                mSecureSettings, mDreamOverlayStateController);
+                mSecureSettings, mDreamOverlayStateController, mMonitor);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamClockTimeComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamClockTimeComplicationTest.java
index ec448f9..52aaea1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamClockTimeComplicationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamClockTimeComplicationTest.java
@@ -30,6 +30,7 @@
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.shared.condition.Monitor;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -69,6 +70,9 @@
     @Mock
     private ComplicationLayoutParams mLayoutParams;
 
+    @Mock
+    private Monitor mMonitor;
+
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
@@ -83,7 +87,8 @@
         final DreamClockTimeComplication.Registrant registrant =
                 new DreamClockTimeComplication.Registrant(
                         mDreamOverlayStateController,
-                        mComplication);
+                        mComplication,
+                        mMonitor);
         registrant.start();
         verify(mDreamOverlayStateController).addComplication(eq(mComplication));
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
index a4cf15c..fc1d38b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
@@ -46,6 +46,7 @@
 import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.dreams.complication.dagger.DreamHomeControlsComplicationComponent;
 import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.shared.condition.Monitor;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -101,6 +102,9 @@
     @Captor
     private ArgumentCaptor<DreamOverlayStateController.Callback> mStateCallbackCaptor;
 
+    @Mock
+    private Monitor mMonitor;
+
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
@@ -126,7 +130,7 @@
     public void complicationAvailability_serviceNotAvailable_noFavorites_doNotAddComplication() {
         final DreamHomeControlsComplication.Registrant registrant =
                 new DreamHomeControlsComplication.Registrant(mComplication,
-                        mDreamOverlayStateController, mControlsComponent);
+                        mDreamOverlayStateController, mControlsComponent, mMonitor);
         registrant.start();
 
         setHaveFavorites(false);
@@ -139,7 +143,7 @@
     public void complicationAvailability_serviceAvailable_noFavorites_doNotAddComplication() {
         final DreamHomeControlsComplication.Registrant registrant =
                 new DreamHomeControlsComplication.Registrant(mComplication,
-                        mDreamOverlayStateController, mControlsComponent);
+                        mDreamOverlayStateController, mControlsComponent, mMonitor);
         registrant.start();
 
         setHaveFavorites(false);
@@ -152,7 +156,7 @@
     public void complicationAvailability_serviceAvailable_noFavorites_panel_addComplication() {
         final DreamHomeControlsComplication.Registrant registrant =
                 new DreamHomeControlsComplication.Registrant(mComplication,
-                        mDreamOverlayStateController, mControlsComponent);
+                        mDreamOverlayStateController, mControlsComponent, mMonitor);
         registrant.start();
 
         setHaveFavorites(false);
@@ -165,7 +169,7 @@
     public void complicationAvailability_serviceNotAvailable_haveFavorites_doNotAddComplication() {
         final DreamHomeControlsComplication.Registrant registrant =
                 new DreamHomeControlsComplication.Registrant(mComplication,
-                        mDreamOverlayStateController, mControlsComponent);
+                        mDreamOverlayStateController, mControlsComponent, mMonitor);
         registrant.start();
 
         setHaveFavorites(true);
@@ -178,7 +182,7 @@
     public void complicationAvailability_serviceAvailable_haveFavorites_addComplication() {
         final DreamHomeControlsComplication.Registrant registrant =
                 new DreamHomeControlsComplication.Registrant(mComplication,
-                        mDreamOverlayStateController, mControlsComponent);
+                        mDreamOverlayStateController, mControlsComponent, mMonitor);
         registrant.start();
 
         setHaveFavorites(true);
@@ -191,7 +195,7 @@
     public void complicationAvailability_checkAvailabilityWhenDreamOverlayBecomesActive() {
         final DreamHomeControlsComplication.Registrant registrant =
                 new DreamHomeControlsComplication.Registrant(mComplication,
-                        mDreamOverlayStateController, mControlsComponent);
+                        mDreamOverlayStateController, mControlsComponent, mMonitor);
         registrant.start();
 
         setServiceAvailable(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/SmartSpaceComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/SmartSpaceComplicationTest.java
index c8b2b25..77ca958 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/SmartSpaceComplicationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/SmartSpaceComplicationTest.java
@@ -33,6 +33,8 @@
 import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.dreams.smartspace.DreamSmartspaceController;
 import com.android.systemui.plugins.BcSmartspaceDataPlugin;
+import com.android.systemui.shared.condition.Condition;
+import com.android.systemui.shared.condition.Monitor;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -43,6 +45,8 @@
 import org.mockito.MockitoAnnotations;
 
 import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
@@ -60,6 +64,11 @@
     @Mock
     private View mBcSmartspaceView;
 
+    @Mock
+    private Monitor mMonitor;
+
+    private final Set<Condition> mPreconditions = new HashSet<>();
+
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
@@ -79,7 +88,8 @@
         return new SmartSpaceComplication.Registrant(
                 mDreamOverlayStateController,
                 mComplication,
-                mSmartspaceController);
+                mSmartspaceController,
+                mMonitor);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewControllerTest.java
index 2290676..c3b0e5226 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewControllerTest.java
@@ -38,6 +38,7 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.keyguard.logging.KeyguardLogger;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.phone.KeyguardIndicationTextView;
@@ -66,6 +67,8 @@
     private KeyguardIndicationTextView mView;
     @Mock
     private StatusBarStateController mStatusBarStateController;
+    @Mock
+    private KeyguardLogger mLogger;
     @Captor
     private ArgumentCaptor<StatusBarStateController.StateListener> mStatusBarStateListenerCaptor;
 
@@ -77,7 +80,7 @@
         MockitoAnnotations.initMocks(this);
         when(mView.getTextColors()).thenReturn(ColorStateList.valueOf(Color.WHITE));
         mController = new KeyguardIndicationRotateTextViewController(mView, mExecutor,
-                mStatusBarStateController);
+                mStatusBarStateController, mLogger);
         mController.onViewAttached();
 
         verify(mStatusBarStateController).addCallback(mStatusBarStateListenerCaptor.capture());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index 2fd39b7..46e4679 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -158,7 +158,7 @@
     }
 
     @Test
-    fun `DREAMING to LOCKSCREEN`() =
+    fun `DREAMING to LOCKSCREEN - dreaming state changes first`() =
         testScope.runTest {
             // GIVEN a device is dreaming and occluded
             keyguardRepository.setDreamingWithOverlay(true)
@@ -188,9 +188,59 @@
             )
             // AND dreaming has stopped
             keyguardRepository.setDreamingWithOverlay(false)
+            advanceUntilIdle()
+            // AND then occluded has stopped
+            keyguardRepository.setKeyguardOccluded(false)
+            advanceUntilIdle()
+
+            val info =
+                withArgCaptor<TransitionInfo> {
+                    verify(mockTransitionRepository).startTransition(capture())
+                }
+            // THEN a transition to BOUNCER should occur
+            assertThat(info.ownerName).isEqualTo("FromDreamingTransitionInteractor")
+            assertThat(info.from).isEqualTo(KeyguardState.DREAMING)
+            assertThat(info.to).isEqualTo(KeyguardState.LOCKSCREEN)
+            assertThat(info.animator).isNotNull()
+
+            coroutineContext.cancelChildren()
+        }
+
+    @Test
+    fun `DREAMING to LOCKSCREEN - occluded state changes first`() =
+        testScope.runTest {
+            // GIVEN a device is dreaming and occluded
+            keyguardRepository.setDreamingWithOverlay(true)
+            keyguardRepository.setKeyguardOccluded(true)
+            runCurrent()
+
+            // GIVEN a prior transition has run to DREAMING
+            runner.startTransition(
+                testScope,
+                TransitionInfo(
+                    ownerName = "",
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.DREAMING,
+                    animator =
+                        ValueAnimator().apply {
+                            duration = 10
+                            interpolator = Interpolators.LINEAR
+                        },
+                )
+            )
+            runCurrent()
+            reset(mockTransitionRepository)
+
+            // WHEN doze is complete
+            keyguardRepository.setDozeTransitionModel(
+                DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
+            )
             // AND occluded has stopped
             keyguardRepository.setKeyguardOccluded(false)
             advanceUntilIdle()
+            // AND then dreaming has stopped
+            keyguardRepository.setDreamingWithOverlay(false)
+            advanceUntilIdle()
 
             val info =
                 withArgCaptor<TransitionInfo> {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/table/LogDiffsForTableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/log/table/LogDiffsForTableTest.kt
index 3b5e6b9..d1744c6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/log/table/LogDiffsForTableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/table/LogDiffsForTableTest.kt
@@ -276,6 +276,52 @@
         }
 
     @Test
+    fun intNullable_logsNull() =
+        testScope.runTest {
+            systemClock.setCurrentTimeMillis(100L)
+            val flow = flow {
+                for (int in listOf(null, 6, null, 8)) {
+                    systemClock.advanceTime(100L)
+                    emit(int)
+                }
+            }
+
+            val flowWithLogging =
+                flow.logDiffsForTable(
+                    tableLogBuffer,
+                    COLUMN_PREFIX,
+                    COLUMN_NAME,
+                    initialValue = 1234,
+                )
+
+            val job = launch { flowWithLogging.collect() }
+
+            val logs = dumpLog()
+            assertThat(logs)
+                .contains(
+                    TABLE_LOG_DATE_FORMAT.format(100L) + SEPARATOR + FULL_NAME + SEPARATOR + "1234"
+                )
+            assertThat(logs)
+                .contains(
+                    TABLE_LOG_DATE_FORMAT.format(200L) + SEPARATOR + FULL_NAME + SEPARATOR + "null"
+                )
+            assertThat(logs)
+                .contains(
+                    TABLE_LOG_DATE_FORMAT.format(300L) + SEPARATOR + FULL_NAME + SEPARATOR + "6"
+                )
+            assertThat(logs)
+                .contains(
+                    TABLE_LOG_DATE_FORMAT.format(400L) + SEPARATOR + FULL_NAME + SEPARATOR + "null"
+                )
+            assertThat(logs)
+                .contains(
+                    TABLE_LOG_DATE_FORMAT.format(500L) + SEPARATOR + FULL_NAME + SEPARATOR + "8"
+                )
+
+            job.cancel()
+        }
+
+    @Test
     fun int_logsUpdates() =
         testScope.runTest {
             systemClock.setCurrentTimeMillis(100L)
@@ -1030,6 +1076,246 @@
             job.cancel()
         }
 
+    // ---- Flow<List<T>> tests ----
+
+    @Test
+    fun list_doesNotLogWhenNotCollected() {
+        val flow = flowOf(listOf(5), listOf(6), listOf(7))
+
+        flow.logDiffsForTable(
+            tableLogBuffer,
+            COLUMN_PREFIX,
+            COLUMN_NAME,
+            initialValue = listOf(1234),
+        )
+
+        val logs = dumpLog()
+        assertThat(logs).doesNotContain(COLUMN_PREFIX)
+        assertThat(logs).doesNotContain(COLUMN_NAME)
+        assertThat(logs).doesNotContain("1234")
+    }
+
+    @Test
+    fun list_logsInitialWhenCollected() =
+        testScope.runTest {
+            val flow = flowOf(listOf(5), listOf(6), listOf(7))
+
+            val flowWithLogging =
+                flow.logDiffsForTable(
+                    tableLogBuffer,
+                    COLUMN_PREFIX,
+                    COLUMN_NAME,
+                    initialValue = listOf(1234),
+                )
+
+            systemClock.setCurrentTimeMillis(3000L)
+            val job = launch { flowWithLogging.collect() }
+
+            val logs = dumpLog()
+            assertThat(logs)
+                .contains(
+                    TABLE_LOG_DATE_FORMAT.format(3000L) +
+                        SEPARATOR +
+                        FULL_NAME +
+                        SEPARATOR +
+                        listOf(1234).toString()
+                )
+
+            job.cancel()
+        }
+
+    @Test
+    fun list_logsUpdates() =
+        testScope.runTest {
+            systemClock.setCurrentTimeMillis(100L)
+
+            val listItems =
+                listOf(listOf("val1", "val2"), listOf("val3"), listOf("val4", "val5", "val6"))
+            val flow = flow {
+                for (list in listItems) {
+                    systemClock.advanceTime(100L)
+                    emit(list)
+                }
+            }
+
+            val flowWithLogging =
+                flow.logDiffsForTable(
+                    tableLogBuffer,
+                    COLUMN_PREFIX,
+                    COLUMN_NAME,
+                    initialValue = listOf("val0", "val00"),
+                )
+
+            val job = launch { flowWithLogging.collect() }
+
+            val logs = dumpLog()
+            assertThat(logs)
+                .contains(
+                    TABLE_LOG_DATE_FORMAT.format(100L) +
+                        SEPARATOR +
+                        FULL_NAME +
+                        SEPARATOR +
+                        listOf("val0", "val00").toString()
+                )
+            assertThat(logs)
+                .contains(
+                    TABLE_LOG_DATE_FORMAT.format(200L) +
+                        SEPARATOR +
+                        FULL_NAME +
+                        SEPARATOR +
+                        listOf("val1", "val2").toString()
+                )
+            assertThat(logs)
+                .contains(
+                    TABLE_LOG_DATE_FORMAT.format(300L) +
+                        SEPARATOR +
+                        FULL_NAME +
+                        SEPARATOR +
+                        listOf("val3").toString()
+                )
+            assertThat(logs)
+                .contains(
+                    TABLE_LOG_DATE_FORMAT.format(400L) +
+                        SEPARATOR +
+                        FULL_NAME +
+                        SEPARATOR +
+                        listOf("val4", "val5", "val6").toString()
+                )
+
+            job.cancel()
+        }
+
+    @Test
+    fun list_doesNotLogIfSameValue() =
+        testScope.runTest {
+            systemClock.setCurrentTimeMillis(100L)
+
+            val listItems =
+                listOf(
+                    listOf("val0", "val00"),
+                    listOf("val1"),
+                    listOf("val1"),
+                    listOf("val1", "val2"),
+                )
+            val flow = flow {
+                for (bool in listItems) {
+                    systemClock.advanceTime(100L)
+                    emit(bool)
+                }
+            }
+
+            val flowWithLogging =
+                flow.logDiffsForTable(
+                    tableLogBuffer,
+                    COLUMN_PREFIX,
+                    COLUMN_NAME,
+                    initialValue = listOf("val0", "val00"),
+                )
+
+            val job = launch { flowWithLogging.collect() }
+
+            val logs = dumpLog()
+
+            val expected1 =
+                TABLE_LOG_DATE_FORMAT.format(100L) +
+                    SEPARATOR +
+                    FULL_NAME +
+                    SEPARATOR +
+                    listOf("val0", "val00").toString()
+            val expected3 =
+                TABLE_LOG_DATE_FORMAT.format(300L) +
+                    SEPARATOR +
+                    FULL_NAME +
+                    SEPARATOR +
+                    listOf("val1").toString()
+            val expected5 =
+                TABLE_LOG_DATE_FORMAT.format(500L) +
+                    SEPARATOR +
+                    FULL_NAME +
+                    SEPARATOR +
+                    listOf("val1", "val2").toString()
+            assertThat(logs).contains(expected1)
+            assertThat(logs).contains(expected3)
+            assertThat(logs).contains(expected5)
+
+            val unexpected2 =
+                TABLE_LOG_DATE_FORMAT.format(200L) +
+                    SEPARATOR +
+                    FULL_NAME +
+                    SEPARATOR +
+                    listOf("val0", "val00")
+            val unexpected4 =
+                TABLE_LOG_DATE_FORMAT.format(400L) +
+                    SEPARATOR +
+                    FULL_NAME +
+                    SEPARATOR +
+                    listOf("val1")
+            assertThat(logs).doesNotContain(unexpected2)
+            assertThat(logs).doesNotContain(unexpected4)
+            job.cancel()
+        }
+
+    @Test
+    fun list_worksForStateFlows() =
+        testScope.runTest {
+            val flow = MutableStateFlow(listOf(1111))
+
+            val flowWithLogging =
+                flow.logDiffsForTable(
+                    tableLogBuffer,
+                    COLUMN_PREFIX,
+                    COLUMN_NAME,
+                    initialValue = listOf(1111),
+                )
+
+            systemClock.setCurrentTimeMillis(50L)
+            val job = launch { flowWithLogging.collect() }
+            assertThat(dumpLog())
+                .contains(
+                    TABLE_LOG_DATE_FORMAT.format(50L) +
+                        SEPARATOR +
+                        FULL_NAME +
+                        SEPARATOR +
+                        listOf(1111).toString()
+                )
+
+            systemClock.setCurrentTimeMillis(100L)
+            flow.emit(listOf(2222, 3333))
+            assertThat(dumpLog())
+                .contains(
+                    TABLE_LOG_DATE_FORMAT.format(100L) +
+                        SEPARATOR +
+                        FULL_NAME +
+                        SEPARATOR +
+                        listOf(2222, 3333).toString()
+                )
+
+            systemClock.setCurrentTimeMillis(200L)
+            flow.emit(listOf(3333, 4444))
+            assertThat(dumpLog())
+                .contains(
+                    TABLE_LOG_DATE_FORMAT.format(200L) +
+                        SEPARATOR +
+                        FULL_NAME +
+                        SEPARATOR +
+                        listOf(3333, 4444).toString()
+                )
+
+            // Doesn't log duplicates
+            systemClock.setCurrentTimeMillis(300L)
+            flow.emit(listOf(3333, 4444))
+            assertThat(dumpLog())
+                .doesNotContain(
+                    TABLE_LOG_DATE_FORMAT.format(300L) +
+                        SEPARATOR +
+                        FULL_NAME +
+                        SEPARATOR +
+                        listOf(3333, 4444).toString()
+                )
+
+            job.cancel()
+        }
+
     private fun dumpLog(): String {
         val outputWriter = StringWriter()
         tableLogBuffer.dump(PrintWriter(outputWriter), arrayOf())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/table/TableChangeTest.kt b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableChangeTest.kt
index 432764a..c7f3fa0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/log/table/TableChangeTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableChangeTest.kt
@@ -36,6 +36,17 @@
     }
 
     @Test
+    fun setString_null() {
+        val underTest = TableChange()
+
+        underTest.reset(timestamp = 100, columnPrefix = "", columnName = "fakeName")
+        underTest.set(null as String?)
+
+        assertThat(underTest.hasData()).isTrue()
+        assertThat(underTest.getVal()).isEqualTo("null")
+    }
+
+    @Test
     fun setBoolean_isBoolean() {
         val underTest = TableChange()
 
@@ -58,6 +69,17 @@
     }
 
     @Test
+    fun setInt_null() {
+        val underTest = TableChange()
+
+        underTest.reset(timestamp = 100, columnPrefix = "", columnName = "fakeName")
+        underTest.set(null as Int?)
+
+        assertThat(underTest.hasData()).isTrue()
+        assertThat(underTest.getVal()).isEqualTo("null")
+    }
+
+    @Test
     fun setThenReset_isEmpty() {
         val underTest = TableChange()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
index 1042ea7..4977775 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
@@ -24,6 +24,8 @@
     private val taskListProvider = TestRecentTaskListProvider()
     private val scope = CoroutineScope(Dispatchers.Unconfined)
     private val appSelectorComponentName = ComponentName("com.test", "AppSelector")
+    private val callerPackageName = "com.test.caller"
+    private val callerComponentName = ComponentName(callerPackageName, "Caller")
 
     private val hostUserHandle = UserHandle.of(123)
     private val otherUserHandle = UserHandle.of(456)
@@ -31,14 +33,16 @@
     private val view: MediaProjectionAppSelectorView = mock()
     private val featureFlags: FeatureFlags = mock()
 
-    private val controller = MediaProjectionAppSelectorController(
-        taskListProvider,
-        view,
-        featureFlags,
-        hostUserHandle,
-        scope,
-        appSelectorComponentName
-    )
+    private val controller =
+        MediaProjectionAppSelectorController(
+            taskListProvider,
+            view,
+            featureFlags,
+            hostUserHandle,
+            scope,
+            appSelectorComponentName,
+            callerPackageName
+        )
 
     @Test
     fun initNoRecentTasks_bindsEmptyList() {
@@ -51,104 +55,87 @@
 
     @Test
     fun initOneRecentTask_bindsList() {
-        taskListProvider.tasks = listOf(
-            createRecentTask(taskId = 1)
-        )
+        taskListProvider.tasks = listOf(createRecentTask(taskId = 1))
 
         controller.init()
 
-        verify(view).bind(
-            listOf(
-                createRecentTask(taskId = 1)
-            )
-        )
+        verify(view).bind(listOf(createRecentTask(taskId = 1)))
     }
 
     @Test
     fun initMultipleRecentTasksWithoutAppSelectorTask_bindsListInTheSameOrder() {
-        val tasks = listOf(
-            createRecentTask(taskId = 1),
-            createRecentTask(taskId = 2),
-            createRecentTask(taskId = 3),
-        )
-        taskListProvider.tasks = tasks
-
-        controller.init()
-
-        verify(view).bind(
+        val tasks =
             listOf(
                 createRecentTask(taskId = 1),
                 createRecentTask(taskId = 2),
                 createRecentTask(taskId = 3),
             )
-        )
-    }
-
-    @Test
-    fun initRecentTasksWithAppSelectorTasks_bindsAppSelectorTasksAtTheEnd() {
-        val tasks = listOf(
-            createRecentTask(taskId = 1),
-            createRecentTask(taskId = 2, topActivityComponent = appSelectorComponentName),
-            createRecentTask(taskId = 3),
-            createRecentTask(taskId = 4, topActivityComponent = appSelectorComponentName),
-            createRecentTask(taskId = 5),
-        )
         taskListProvider.tasks = tasks
 
         controller.init()
 
-        verify(view).bind(
+        verify(view)
+            .bind(
+                listOf(
+                    createRecentTask(taskId = 1),
+                    createRecentTask(taskId = 2),
+                    createRecentTask(taskId = 3),
+                )
+            )
+    }
+
+    @Test
+    fun initRecentTasksWithAppSelectorTasks_removeAppSelector() {
+        val tasks =
             listOf(
                 createRecentTask(taskId = 1),
-                createRecentTask(taskId = 3),
-                createRecentTask(taskId = 5),
                 createRecentTask(taskId = 2, topActivityComponent = appSelectorComponentName),
-                createRecentTask(taskId = 4, topActivityComponent = appSelectorComponentName),
+                createRecentTask(taskId = 3),
+                createRecentTask(taskId = 4),
             )
-        )
+        taskListProvider.tasks = tasks
+
+        controller.init()
+
+        verify(view)
+            .bind(
+                listOf(
+                    createRecentTask(taskId = 1),
+                    createRecentTask(taskId = 3),
+                    createRecentTask(taskId = 4),
+                )
+            )
+    }
+
+    @Test
+    fun initRecentTasksWithAppSelectorTasks_bindsCallerTasksAtTheEnd() {
+        val tasks =
+            listOf(
+                createRecentTask(taskId = 1),
+                createRecentTask(taskId = 2, topActivityComponent = callerComponentName),
+                createRecentTask(taskId = 3),
+                createRecentTask(taskId = 4),
+            )
+        taskListProvider.tasks = tasks
+
+        controller.init()
+
+        verify(view)
+            .bind(
+                listOf(
+                    createRecentTask(taskId = 1),
+                    createRecentTask(taskId = 3),
+                    createRecentTask(taskId = 4),
+                    createRecentTask(taskId = 2, topActivityComponent = callerComponentName),
+                )
+            )
     }
 
     @Test
     fun initRecentTasksWithAppSelectorTasks_enterprisePoliciesDisabled_bindsOnlyTasksWithHostProfile() {
         givenEnterprisePoliciesFeatureFlag(enabled = false)
 
-        val tasks = listOf(
-            createRecentTask(taskId = 1, userId = hostUserHandle.identifier),
-            createRecentTask(taskId = 2, userId = otherUserHandle.identifier),
-            createRecentTask(taskId = 3, userId = hostUserHandle.identifier),
-            createRecentTask(taskId = 4, userId = otherUserHandle.identifier),
-            createRecentTask(taskId = 5, userId = hostUserHandle.identifier),
-        )
-        taskListProvider.tasks = tasks
-
-        controller.init()
-
-        verify(view).bind(
-            listOf(
-                createRecentTask(taskId = 1, userId = hostUserHandle.identifier),
-                createRecentTask(taskId = 3, userId = hostUserHandle.identifier),
-                createRecentTask(taskId = 5, userId = hostUserHandle.identifier),
-            )
-        )
-    }
-
-    @Test
-    fun initRecentTasksWithAppSelectorTasks_enterprisePoliciesEnabled_bindsAllTasks() {
-        givenEnterprisePoliciesFeatureFlag(enabled = true)
-
-        val tasks = listOf(
-            createRecentTask(taskId = 1, userId = hostUserHandle.identifier),
-            createRecentTask(taskId = 2, userId = otherUserHandle.identifier),
-            createRecentTask(taskId = 3, userId = hostUserHandle.identifier),
-            createRecentTask(taskId = 4, userId = otherUserHandle.identifier),
-            createRecentTask(taskId = 5, userId = hostUserHandle.identifier),
-        )
-        taskListProvider.tasks = tasks
-
-        controller.init()
-
-        // TODO(b/233348916) should filter depending on the policies
-        verify(view).bind(
+        val tasks =
             listOf(
                 createRecentTask(taskId = 1, userId = hostUserHandle.identifier),
                 createRecentTask(taskId = 2, userId = otherUserHandle.identifier),
@@ -156,7 +143,47 @@
                 createRecentTask(taskId = 4, userId = otherUserHandle.identifier),
                 createRecentTask(taskId = 5, userId = hostUserHandle.identifier),
             )
-        )
+        taskListProvider.tasks = tasks
+
+        controller.init()
+
+        verify(view)
+            .bind(
+                listOf(
+                    createRecentTask(taskId = 1, userId = hostUserHandle.identifier),
+                    createRecentTask(taskId = 3, userId = hostUserHandle.identifier),
+                    createRecentTask(taskId = 5, userId = hostUserHandle.identifier),
+                )
+            )
+    }
+
+    @Test
+    fun initRecentTasksWithAppSelectorTasks_enterprisePoliciesEnabled_bindsAllTasks() {
+        givenEnterprisePoliciesFeatureFlag(enabled = true)
+
+        val tasks =
+            listOf(
+                createRecentTask(taskId = 1, userId = hostUserHandle.identifier),
+                createRecentTask(taskId = 2, userId = otherUserHandle.identifier),
+                createRecentTask(taskId = 3, userId = hostUserHandle.identifier),
+                createRecentTask(taskId = 4, userId = otherUserHandle.identifier),
+                createRecentTask(taskId = 5, userId = hostUserHandle.identifier),
+            )
+        taskListProvider.tasks = tasks
+
+        controller.init()
+
+        // TODO(b/233348916) should filter depending on the policies
+        verify(view)
+            .bind(
+                listOf(
+                    createRecentTask(taskId = 1, userId = hostUserHandle.identifier),
+                    createRecentTask(taskId = 2, userId = otherUserHandle.identifier),
+                    createRecentTask(taskId = 3, userId = hostUserHandle.identifier),
+                    createRecentTask(taskId = 4, userId = otherUserHandle.identifier),
+                    createRecentTask(taskId = 5, userId = hostUserHandle.identifier),
+                )
+            )
     }
 
     private fun givenEnterprisePoliciesFeatureFlag(enabled: Boolean) {
@@ -183,6 +210,5 @@
         var tasks: List<RecentTask> = emptyList()
 
         override suspend fun loadRecentTasks(): List<RecentTask> = tasks
-
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/MotionEventsHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/MotionEventsHandlerTest.java
deleted file mode 100644
index 70ba306..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/MotionEventsHandlerTest.java
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.navigationbar.gestural;
-
-import static android.view.InputDevice.SOURCE_MOUSE;
-import static android.view.MotionEvent.AXIS_GESTURE_X_OFFSET;
-import static android.view.MotionEvent.AXIS_GESTURE_Y_OFFSET;
-import static android.view.MotionEvent.CLASSIFICATION_MULTI_FINGER_SWIPE;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.view.MotionEvent;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.flags.FakeFeatureFlags;
-import com.android.systemui.flags.Flags;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Tests for {@link MotionEventsHandler}.
- */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class MotionEventsHandlerTest extends SysuiTestCase {
-
-    private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
-    private static int SCALE = 100;
-
-    private MotionEventsHandler mMotionEventsHandler;
-
-    @Before
-    public void setUp() {
-        mFeatureFlags.set(Flags.TRACKPAD_GESTURE_BACK, true);
-        mMotionEventsHandler = new MotionEventsHandler(mFeatureFlags, SCALE);
-    }
-
-    @Test
-    public void onTouchEvent_touchScreen_hasCorrectDisplacements() {
-        MotionEvent down = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 100, 100, 0);
-        MotionEvent move1 = MotionEvent.obtain(0, 1, MotionEvent.ACTION_MOVE, 150, 125, 0);
-        MotionEvent move2 = MotionEvent.obtain(0, 2, MotionEvent.ACTION_MOVE, 200, 150, 0);
-        MotionEvent up = MotionEvent.obtain(0, 3, MotionEvent.ACTION_UP, 250, 175, 0);
-
-        mMotionEventsHandler.onMotionEvent(down);
-        mMotionEventsHandler.onMotionEvent(move1);
-        assertThat(mMotionEventsHandler.getDisplacementX(move1)).isEqualTo(50);
-        assertThat(mMotionEventsHandler.getDisplacementY(move1)).isEqualTo(25);
-        mMotionEventsHandler.onMotionEvent(move2);
-        assertThat(mMotionEventsHandler.getDisplacementX(move2)).isEqualTo(100);
-        assertThat(mMotionEventsHandler.getDisplacementY(move2)).isEqualTo(50);
-        mMotionEventsHandler.onMotionEvent(up);
-        assertThat(mMotionEventsHandler.getDisplacementX(up)).isEqualTo(150);
-        assertThat(mMotionEventsHandler.getDisplacementY(up)).isEqualTo(75);
-    }
-
-    @Test
-    public void onTouchEvent_trackpad_hasCorrectDisplacements() {
-        MotionEvent.PointerCoords[] downPointerCoords = new MotionEvent.PointerCoords[1];
-        downPointerCoords[0] = new MotionEvent.PointerCoords();
-        downPointerCoords[0].setAxisValue(AXIS_GESTURE_X_OFFSET, 0.1f);
-        downPointerCoords[0].setAxisValue(AXIS_GESTURE_Y_OFFSET, 0.1f);
-        MotionEvent.PointerProperties[] downPointerProperties =
-                new MotionEvent.PointerProperties[1];
-        downPointerProperties[0] = new MotionEvent.PointerProperties();
-        downPointerProperties[0].id = 1;
-        downPointerProperties[0].toolType = MotionEvent.TOOL_TYPE_FINGER;
-        MotionEvent down = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 1,
-                downPointerProperties, downPointerCoords, 0, 0, 1.0f, 1.0f, 0, 0, SOURCE_MOUSE,
-                0, 0, CLASSIFICATION_MULTI_FINGER_SWIPE);
-
-        MotionEvent.PointerCoords[] movePointerCoords1 = new MotionEvent.PointerCoords[1];
-        movePointerCoords1[0] = new MotionEvent.PointerCoords();
-        movePointerCoords1[0].setAxisValue(AXIS_GESTURE_X_OFFSET, 0.2f);
-        movePointerCoords1[0].setAxisValue(AXIS_GESTURE_Y_OFFSET, 0.1f);
-        MotionEvent.PointerProperties[] movePointerProperties1 =
-                new MotionEvent.PointerProperties[1];
-        movePointerProperties1[0] = new MotionEvent.PointerProperties();
-        movePointerProperties1[0].id = 1;
-        movePointerProperties1[0].toolType = MotionEvent.TOOL_TYPE_FINGER;
-        MotionEvent move1 = MotionEvent.obtain(0, 1, MotionEvent.ACTION_MOVE, 1,
-                movePointerProperties1, movePointerCoords1, 0, 0, 1.0f, 1.0f, 0, 0, SOURCE_MOUSE,
-                0, 0, CLASSIFICATION_MULTI_FINGER_SWIPE);
-
-        MotionEvent.PointerCoords[] movePointerCoords2 = new MotionEvent.PointerCoords[1];
-        movePointerCoords2[0] = new MotionEvent.PointerCoords();
-        movePointerCoords2[0].setAxisValue(AXIS_GESTURE_X_OFFSET, 0.1f);
-        movePointerCoords2[0].setAxisValue(AXIS_GESTURE_Y_OFFSET, 0.4f);
-        MotionEvent.PointerProperties[] movePointerProperties2 =
-                new MotionEvent.PointerProperties[1];
-        movePointerProperties2[0] = new MotionEvent.PointerProperties();
-        movePointerProperties2[0].id = 1;
-        movePointerProperties2[0].toolType = MotionEvent.TOOL_TYPE_FINGER;
-        MotionEvent move2 = MotionEvent.obtain(0, 2, MotionEvent.ACTION_MOVE, 1,
-                movePointerProperties2, movePointerCoords2, 0, 0, 1.0f, 1.0f, 0, 0, SOURCE_MOUSE,
-                0, 0, CLASSIFICATION_MULTI_FINGER_SWIPE);
-
-        MotionEvent.PointerCoords[] upPointerCoords = new MotionEvent.PointerCoords[1];
-        upPointerCoords[0] = new MotionEvent.PointerCoords();
-        upPointerCoords[0].setAxisValue(AXIS_GESTURE_X_OFFSET, 0.1f);
-        upPointerCoords[0].setAxisValue(AXIS_GESTURE_Y_OFFSET, 0.1f);
-        MotionEvent.PointerProperties[] upPointerProperties2 =
-                new MotionEvent.PointerProperties[1];
-        upPointerProperties2[0] = new MotionEvent.PointerProperties();
-        upPointerProperties2[0].id = 1;
-        upPointerProperties2[0].toolType = MotionEvent.TOOL_TYPE_FINGER;
-        MotionEvent up = MotionEvent.obtain(0, 2, MotionEvent.ACTION_UP, 1,
-                upPointerProperties2, upPointerCoords, 0, 0, 1.0f, 1.0f, 0, 0, SOURCE_MOUSE,
-                0, 0, CLASSIFICATION_MULTI_FINGER_SWIPE);
-
-        mMotionEventsHandler.onMotionEvent(down);
-        mMotionEventsHandler.onMotionEvent(move1);
-        assertThat(mMotionEventsHandler.getDisplacementX(move1)).isEqualTo(20f);
-        assertThat(mMotionEventsHandler.getDisplacementY(move1)).isEqualTo(10f);
-        mMotionEventsHandler.onMotionEvent(move2);
-        assertThat(mMotionEventsHandler.getDisplacementX(move2)).isEqualTo(30f);
-        assertThat(mMotionEventsHandler.getDisplacementY(move2)).isEqualTo(50f);
-        mMotionEventsHandler.onMotionEvent(up);
-        assertThat(mMotionEventsHandler.getDisplacementX(up)).isEqualTo(40f);
-        assertThat(mMotionEventsHandler.getDisplacementY(up)).isEqualTo(60f);
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index 1ac7eaa..3b4cc7c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -23,6 +23,7 @@
 import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT;
 import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_TIMEOUT;
 
+import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_AVAILABLE;
 import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED;
 import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED;
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_ALIGNMENT;
@@ -631,6 +632,33 @@
     }
 
     @Test
+    public void onBiometricHelp_coEx_faceUnavailable() {
+        createController();
+
+        // GIVEN unlocking with fingerprint is possible
+        when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(anyInt()))
+                .thenReturn(true);
+
+        String message = "A message";
+        mController.setVisible(true);
+
+        // WHEN there's a face unavailable message
+        mController.getKeyguardCallback().onBiometricHelp(
+                BIOMETRIC_HELP_FACE_NOT_AVAILABLE,
+                message,
+                BiometricSourceType.FACE);
+
+        // THEN show sequential messages such as: 'face unlock unavailable' and
+        // 'try fingerprint instead'
+        verifyIndicationMessage(
+                INDICATION_TYPE_BIOMETRIC_MESSAGE,
+                message);
+        verifyIndicationMessage(
+                INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
+                mContext.getString(R.string.keyguard_suggest_fingerprint));
+    }
+
+    @Test
     public void onBiometricHelp_coEx_fpFailure_faceAlreadyUnlocked() {
         createController();
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinatorTest.java
index 701cf95..af52459 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinatorTest.java
@@ -117,10 +117,6 @@
         extras.putBoolean(SHOW_WHEN_UNPROVISIONED_FLAG, true);
         mNotification.extras = extras;
 
-        // GIVEN notification has the permission to display during setup
-        when(mIPackageManager.checkUidPermission(SETUP_NOTIF_PERMISSION, NOTIF_UID))
-                .thenReturn(PackageManager.PERMISSION_GRANTED);
-
         // THEN don't filter out the notification
         assertFalse(mDeviceProvisionedFilter.shouldFilterOut(mEntry, 0));
     }
@@ -130,15 +126,10 @@
         // GIVEN device is unprovisioned
         when(mDeviceProvisionedController.isDeviceProvisioned()).thenReturn(false);
 
-        // GIVEN notification has a flag to allow the notification during setup
+        // GIVEN notification does not have the flag to allow the notification during setup
         Bundle extras = new Bundle();
-        extras.putBoolean(SHOW_WHEN_UNPROVISIONED_FLAG, true);
         mNotification.extras = extras;
 
-        // GIVEN notification does NOT have permission to display during setup
-        when(mIPackageManager.checkUidPermission(SETUP_NOTIF_PERMISSION, NOTIF_UID))
-                .thenReturn(PackageManager.PERMISSION_DENIED);
-
         // THEN filter out the notification
         assertTrue(mDeviceProvisionedFilter.shouldFilterOut(mEntry, 0));
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
index 2686238..49da848 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
@@ -38,6 +38,8 @@
 import com.android.systemui.statusbar.notification.collection.provider.SeenNotificationsProviderImpl
 import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+import com.android.systemui.statusbar.policy.HeadsUpManager
+import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.withArgCaptor
@@ -63,6 +65,7 @@
 @RunWith(AndroidTestingRunner::class)
 class KeyguardCoordinatorTest : SysuiTestCase() {
 
+    private val headsUpManager: HeadsUpManager = mock()
     private val keyguardNotifVisibilityProvider: KeyguardNotificationVisibilityProvider = mock()
     private val keyguardRepository = FakeKeyguardRepository()
     private val notifPipelineFlags: NotifPipelineFlags = mock()
@@ -90,8 +93,9 @@
     fun unseenFilterSuppressesSeenNotifWhileKeyguardShowing() {
         whenever(notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard).thenReturn(true)
 
-        // GIVEN: Keyguard is not showing, and a notification is present
+        // GIVEN: Keyguard is not showing, shade is expanded, and a notification is present
         keyguardRepository.setKeyguardShowing(false)
+        whenever(statusBarStateController.isExpanded).thenReturn(true)
         runKeyguardCoordinatorTest {
             val fakeEntry = NotificationEntryBuilder().build()
             collectionListener.onEntryAdded(fakeEntry)
@@ -113,11 +117,44 @@
     }
 
     @Test
+    fun unseenFilter_headsUpMarkedAsSeen() {
+        whenever(notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard).thenReturn(true)
+
+        // GIVEN: Keyguard is not showing, shade is not expanded
+        keyguardRepository.setKeyguardShowing(false)
+        whenever(statusBarStateController.isExpanded).thenReturn(false)
+        runKeyguardCoordinatorTest {
+            // WHEN: A notification is posted
+            val fakeEntry = NotificationEntryBuilder().build()
+            collectionListener.onEntryAdded(fakeEntry)
+
+            // WHEN: That notification is heads up
+            onHeadsUpChangedListener.onHeadsUpStateChanged(fakeEntry, /* isHeadsUp= */ true)
+            testScheduler.runCurrent()
+
+            // WHEN: The keyguard is now showing
+            keyguardRepository.setKeyguardShowing(true)
+            testScheduler.runCurrent()
+
+            // THEN: The notification is recognized as "seen" and is filtered out.
+            assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isTrue()
+
+            // WHEN: The keyguard goes away
+            keyguardRepository.setKeyguardShowing(false)
+            testScheduler.runCurrent()
+
+            // THEN: The notification is shown regardless
+            assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse()
+        }
+    }
+
+    @Test
     fun unseenFilterDoesNotSuppressSeenOngoingNotifWhileKeyguardShowing() {
         whenever(notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard).thenReturn(true)
 
-        // GIVEN: Keyguard is not showing, and an ongoing notification is present
+        // GIVEN: Keyguard is not showing, shade is expanded, and an ongoing notification is present
         keyguardRepository.setKeyguardShowing(false)
+        whenever(statusBarStateController.isExpanded).thenReturn(true)
         runKeyguardCoordinatorTest {
             val fakeEntry = NotificationEntryBuilder()
                 .setNotification(Notification.Builder(mContext).setOngoing(true).build())
@@ -137,8 +174,9 @@
     fun unseenFilterDoesNotSuppressSeenMediaNotifWhileKeyguardShowing() {
         whenever(notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard).thenReturn(true)
 
-        // GIVEN: Keyguard is not showing, and a media notification is present
+        // GIVEN: Keyguard is not showing, shade is expanded, and a media notification is present
         keyguardRepository.setKeyguardShowing(false)
+        whenever(statusBarStateController.isExpanded).thenReturn(true)
         runKeyguardCoordinatorTest {
             val fakeEntry = NotificationEntryBuilder().build().apply {
                 row = mock<ExpandableNotificationRow>().apply {
@@ -160,8 +198,9 @@
     fun unseenFilterUpdatesSeenProviderWhenSuppressing() {
         whenever(notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard).thenReturn(true)
 
-        // GIVEN: Keyguard is not showing, and a notification is present
+        // GIVEN: Keyguard is not showing, shade is expanded, and a notification is present
         keyguardRepository.setKeyguardShowing(false)
+        whenever(statusBarStateController.isExpanded).thenReturn(true)
         runKeyguardCoordinatorTest {
             val fakeEntry = NotificationEntryBuilder().build()
             collectionListener.onEntryAdded(fakeEntry)
@@ -185,8 +224,9 @@
     fun unseenFilterInvalidatesWhenSettingChanges() {
         whenever(notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard).thenReturn(true)
 
-        // GIVEN: Keyguard is not showing
+        // GIVEN: Keyguard is not showing, and shade is expanded
         keyguardRepository.setKeyguardShowing(false)
+        whenever(statusBarStateController.isExpanded).thenReturn(true)
         runKeyguardCoordinatorTest {
             // GIVEN: A notification is present
             val fakeEntry = NotificationEntryBuilder().build()
@@ -237,8 +277,9 @@
     fun unseenFilterSeenGroupSummaryWithUnseenChild() {
         whenever(notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard).thenReturn(true)
 
-        // GIVEN: Keyguard is not showing, and a notification is present
+        // GIVEN: Keyguard is not showing, shade is expanded, and a notification is present
         keyguardRepository.setKeyguardShowing(false)
+        whenever(statusBarStateController.isExpanded).thenReturn(true)
         runKeyguardCoordinatorTest {
             // WHEN: A new notification is posted
             val fakeSummary = NotificationEntryBuilder().build()
@@ -276,11 +317,11 @@
             val fakeEntry = NotificationEntryBuilder().build()
             collectionListener.onEntryAdded(fakeEntry)
 
-            // WHEN: Keyguard is no longer showing for 5 seconds
+            // WHEN: Keyguard is no longer showing
             keyguardRepository.setKeyguardShowing(false)
-            testScheduler.runCurrent()
-            testScheduler.advanceTimeBy(5.seconds.inWholeMilliseconds)
-            testScheduler.runCurrent()
+
+            // When: Shade is expanded
+            statusBarStateListener.onExpandedChanged(true)
 
             // WHEN: Keyguard is shown again
             keyguardRepository.setKeyguardShowing(true)
@@ -292,7 +333,7 @@
     }
 
     @Test
-    fun unseenNotificationIsNotMarkedAsSeenIfTimeThresholdNotMet() {
+    fun unseenNotificationIsNotMarkedAsSeenIfShadeNotExpanded() {
         whenever(notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard).thenReturn(true)
 
         // GIVEN: Keyguard is showing, unseen notification is present
@@ -301,10 +342,8 @@
             val fakeEntry = NotificationEntryBuilder().build()
             collectionListener.onEntryAdded(fakeEntry)
 
-            // WHEN: Keyguard is no longer showing for <5 seconds
+            // WHEN: Keyguard is no longer showing
             keyguardRepository.setKeyguardShowing(false)
-            testScheduler.runCurrent()
-            testScheduler.advanceTimeBy(1.seconds.inWholeMilliseconds)
 
             // WHEN: Keyguard is shown again
             keyguardRepository.setKeyguardShowing(true)
@@ -327,6 +366,7 @@
         val keyguardCoordinator =
             KeyguardCoordinator(
                 testDispatcher,
+                headsUpManager,
                 keyguardNotifVisibilityProvider,
                 keyguardRepository,
                 notifPipelineFlags,
@@ -364,12 +404,21 @@
         val unseenFilter: NotifFilter
             get() = keyguardCoordinator.unseenNotifFilter
 
-        // TODO(254647461): Remove lazy once Flags.FILTER_UNSEEN_NOTIFS_ON_KEYGUARD is enabled and
-        //  removed
+        // TODO(254647461): Remove lazy from these properties once
+        //    Flags.FILTER_UNSEEN_NOTIFS_ON_KEYGUARD is enabled and removed
+
         val collectionListener: NotifCollectionListener by lazy {
             withArgCaptor { verify(notifPipeline).addCollectionListener(capture()) }
         }
 
+        val onHeadsUpChangedListener: OnHeadsUpChangedListener by lazy {
+            withArgCaptor { verify(headsUpManager).addListener(capture()) }
+        }
+
+        val statusBarStateListener: StatusBarStateController.StateListener by lazy {
+            withArgCaptor { verify(statusBarStateController).addCallback(capture()) }
+        }
+
         var showOnlyUnseenNotifsOnKeyguardSetting: Boolean
             get() =
                 fakeSettings.getIntForUser(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
index 5394d88..3face35 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
@@ -24,6 +24,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
@@ -42,6 +43,7 @@
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
+import android.util.TypedValue;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.RemoteViews;
@@ -332,6 +334,38 @@
                 eq(FLAG_CONTENT_VIEW_HEADS_UP));
     }
 
+    @Test
+    public void testNotificationViewHeightTooSmallFailsValidation() {
+        View view = mock(View.class);
+        when(view.getHeight())
+                .thenReturn((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 10,
+                        mContext.getResources().getDisplayMetrics()));
+        String result = NotificationContentInflater.isValidView(view, mRow.getEntry(),
+                mContext.getResources());
+        assertNotNull(result);
+    }
+
+    @Test
+    public void testNotificationViewPassesValidation() {
+        View view = mock(View.class);
+        when(view.getHeight())
+                .thenReturn((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 17,
+                        mContext.getResources().getDisplayMetrics()));
+        String result = NotificationContentInflater.isValidView(view, mRow.getEntry(),
+                mContext.getResources());
+        assertNull(result);
+    }
+
+    @Test
+    public void testInvalidNotificationDoesNotInvokeCallback() throws Exception {
+        mRow.getPrivateLayout().removeAllViews();
+        mRow.getEntry().getSbn().getNotification().contentView =
+                new RemoteViews(mContext.getPackageName(), R.layout.invalid_notification_height);
+        inflateAndWait(true, mNotificationInflater, FLAG_CONTENT_VIEW_ALL, mRow);
+        assertEquals(0, mRow.getPrivateLayout().getChildCount());
+        verify(mRow, times(0)).onNotificationUpdated();
+    }
+
     private static void inflateAndWait(NotificationContentInflater inflater,
             @InflationFlag int contentToInflate,
             ExpandableNotificationRow row)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
index 8cfcc07..f568547 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.phone;
 
 import static com.android.systemui.qs.dagger.QSFlagsModule.RBC_AVAILABLE;
+import static com.android.systemui.statusbar.phone.AutoTileManager.DEVICE_CONTROLS;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -50,6 +51,7 @@
 
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.qs.AutoAddTracker;
 import com.android.systemui.qs.QSTileHost;
 import com.android.systemui.qs.ReduceBrightColorsController;
@@ -70,6 +72,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Answers;
+import org.mockito.ArgumentCaptor;
 import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.Mockito;
@@ -77,6 +80,7 @@
 import org.mockito.Spy;
 import org.mockito.stubbing.Answer;
 
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
@@ -543,6 +547,61 @@
     }
 
     @Test
+    public void testAddControlsTileIfNotPresent() {
+        String spec = DEVICE_CONTROLS;
+        when(mAutoAddTracker.isAdded(eq(spec))).thenReturn(false);
+        when(mQsTileHost.getTiles()).thenReturn(new ArrayList<>());
+
+        mAutoTileManager.init();
+        ArgumentCaptor<DeviceControlsController.Callback> captor =
+                ArgumentCaptor.forClass(DeviceControlsController.Callback.class);
+
+        verify(mDeviceControlsController).setCallback(captor.capture());
+
+        captor.getValue().onControlsUpdate(3);
+        verify(mQsTileHost).addTile(spec, 3);
+        verify(mAutoAddTracker).setTileAdded(spec);
+    }
+
+    @Test
+    public void testDontAddControlsTileIfPresent() {
+        String spec = DEVICE_CONTROLS;
+        when(mAutoAddTracker.isAdded(eq(spec))).thenReturn(false);
+        when(mQsTileHost.getTiles()).thenReturn(new ArrayList<>());
+
+        mAutoTileManager.init();
+        ArgumentCaptor<DeviceControlsController.Callback> captor =
+                ArgumentCaptor.forClass(DeviceControlsController.Callback.class);
+
+        verify(mDeviceControlsController).setCallback(captor.capture());
+
+        captor.getValue().removeControlsAutoTracker();
+        verify(mQsTileHost, never()).addTile(spec, 3);
+        verify(mAutoAddTracker, never()).setTileAdded(spec);
+        verify(mAutoAddTracker).setTileRemoved(spec);
+    }
+
+    @Test
+    public void testRemoveControlsTileFromTrackerWhenRequested() {
+        String spec = "controls";
+        when(mAutoAddTracker.isAdded(eq(spec))).thenReturn(true);
+        QSTile mockTile = mock(QSTile.class);
+        when(mockTile.getTileSpec()).thenReturn(spec);
+        when(mQsTileHost.getTiles()).thenReturn(List.of(mockTile));
+
+        mAutoTileManager.init();
+        ArgumentCaptor<DeviceControlsController.Callback> captor =
+                ArgumentCaptor.forClass(DeviceControlsController.Callback.class);
+
+        verify(mDeviceControlsController).setCallback(captor.capture());
+
+        captor.getValue().onControlsUpdate(3);
+        verify(mQsTileHost, never()).addTile(spec, 3);
+        verify(mAutoAddTracker, never()).setTileAdded(spec);
+    }
+
+
+    @Test
     public void testEmptyArray_doesNotCrash() {
         mContext.getOrCreateTestableResources().addOverride(
                 R.array.config_quickSettingsAutoAdd, new String[0]);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
index 6fb6893..8aaa57f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
@@ -23,6 +23,8 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.testing.AndroidTestingRunner;
@@ -33,7 +35,10 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.statusbar.StatusBarIcon;
+import com.android.systemui.demomode.DemoModeController;
+import com.android.systemui.dump.DumpManager;
 import com.android.systemui.plugins.DarkIconDispatcher;
+import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.StatusBarIconView;
 import com.android.systemui.statusbar.StatusBarMobileView;
 import com.android.systemui.statusbar.StatusBarWifiView;
@@ -46,6 +51,8 @@
 import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
 import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter;
 import com.android.systemui.statusbar.pipeline.wifi.ui.WifiUiAdapter;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.tuner.TunerService;
 import com.android.systemui.utils.leaks.LeakCheckedTest;
 
 import org.junit.Before;
@@ -87,6 +94,61 @@
         testCallOnAdd_forManager(manager);
     }
 
+    @Test
+    public void testRemoveIcon_ignoredForNewPipeline() {
+        IconManager manager = mock(IconManager.class);
+
+        // GIVEN the new pipeline is on
+        StatusBarPipelineFlags flags = mock(StatusBarPipelineFlags.class);
+        when(flags.isIconControlledByFlags("test_icon")).thenReturn(true);
+
+        StatusBarIconController iconController = new StatusBarIconControllerImpl(
+                mContext,
+                mock(CommandQueue.class),
+                mock(DemoModeController.class),
+                mock(ConfigurationController.class),
+                mock(TunerService.class),
+                mock(DumpManager.class),
+                mock(StatusBarIconList.class),
+                flags
+        );
+
+        iconController.addIconGroup(manager);
+
+        // WHEN a request to remove a new icon is sent
+        iconController.removeIcon("test_icon", 0);
+
+        // THEN it is not removed for those icons
+        verify(manager, never()).onRemoveIcon(anyInt());
+    }
+
+    @Test
+    public void testRemoveAllIconsForSlot_ignoredForNewPipeline() {
+        IconManager manager = mock(IconManager.class);
+
+        // GIVEN the new pipeline is on
+        StatusBarPipelineFlags flags = mock(StatusBarPipelineFlags.class);
+        when(flags.isIconControlledByFlags("test_icon")).thenReturn(true);
+
+        StatusBarIconController iconController = new StatusBarIconControllerImpl(
+                mContext,
+                mock(CommandQueue.class),
+                mock(DemoModeController.class),
+                mock(ConfigurationController.class),
+                mock(TunerService.class),
+                mock(DumpManager.class),
+                mock(StatusBarIconList.class),
+                flags
+        );
+
+        iconController.addIconGroup(manager);
+
+        // WHEN a request to remove a new icon is sent
+        iconController.removeAllIconsForSlot("test_icon");
+
+        // THEN it is not removed for those icons
+        verify(manager, never()).onRemoveIcon(anyInt());
+    }
 
     private <T extends IconManager & TestableIconManager> void testCallOnAdd_forManager(T manager) {
         StatusBarIconHolder holder = holderForType(TYPE_ICON);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
index cb9eb70..f483e42 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
@@ -55,8 +55,12 @@
     private val _subscriptions = MutableStateFlow<List<SubscriptionModel>>(listOf())
     override val subscriptions = _subscriptions
 
-    private val _activeMobileDataSubscriptionId = MutableStateFlow(INVALID_SUBSCRIPTION_ID)
+    private val _activeMobileDataSubscriptionId = MutableStateFlow<Int?>(null)
     override val activeMobileDataSubscriptionId = _activeMobileDataSubscriptionId
+
+    private val _activeMobileRepository = MutableStateFlow<MobileConnectionRepository?>(null)
+    override val activeMobileDataRepository = _activeMobileRepository
+
     override val activeSubChangedInGroupEvent: MutableSharedFlow<Unit> = MutableSharedFlow()
 
     private val _defaultDataSubId = MutableStateFlow(INVALID_SUBSCRIPTION_ID)
@@ -66,6 +70,7 @@
     override val defaultMobileNetworkConnectivity = _mobileConnectivity
 
     private val subIdRepos = mutableMapOf<Int, MobileConnectionRepository>()
+
     override fun getRepoForSubId(subId: Int): MobileConnectionRepository {
         return subIdRepos[subId]
             ?: FakeMobileConnectionRepository(subId, tableLogBuffer).also { subIdRepos[subId] = it }
@@ -92,7 +97,14 @@
     }
 
     fun setActiveMobileDataSubscriptionId(subId: Int) {
-        _activeMobileDataSubscriptionId.value = subId
+        // Simulate the filtering that the repo does
+        if (subId == INVALID_SUBSCRIPTION_ID) {
+            _activeMobileDataSubscriptionId.value = null
+            _activeMobileRepository.value = null
+        } else {
+            _activeMobileDataSubscriptionId.value = subId
+            _activeMobileRepository.value = getRepoForSubId(subId)
+        }
     }
 
     fun setMobileConnectionRepositoryMap(connections: Map<Int, MobileConnectionRepository>) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
index 96cca44..4da2104 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.demomode.DemoMode
 import com.android.systemui.demomode.DemoModeController
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.log.table.TableLogBufferFactory
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.DemoMobileConnectionsRepository
@@ -81,6 +82,7 @@
     @Mock private lateinit var subscriptionManager: SubscriptionManager
     @Mock private lateinit var telephonyManager: TelephonyManager
     @Mock private lateinit var logger: ConnectivityPipelineLogger
+    @Mock private lateinit var summaryLogger: TableLogBuffer
     @Mock private lateinit var demoModeController: DemoModeController
     @Mock private lateinit var dumpManager: DumpManager
 
@@ -114,6 +116,7 @@
                 subscriptionManager,
                 telephonyManager,
                 logger,
+                summaryLogger,
                 mobileMappings,
                 fakeBroadcastDispatcher,
                 context,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt
index 6989b514..00ce412 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt
@@ -138,7 +138,8 @@
                 assertThat(connectionInfo.carrierNetworkChangeActive)
                     .isEqualTo(model.carrierNetworkChange)
                 assertThat(connectionInfo.isRoaming).isEqualTo(model.roaming)
-                assertThat(conn.networkName.value).isEqualTo(NetworkNameModel.Derived(model.name))
+                assertThat(conn.networkName.value)
+                    .isEqualTo(NetworkNameModel.IntentDerived(model.name))
 
                 // TODO(b/261029387): check these once we start handling them
                 assertThat(connectionInfo.isEmergencyOnly).isFalse()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt
index f12d113..f60d92b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt
@@ -539,7 +539,8 @@
                 assertThat(connectionInfo.carrierNetworkChangeActive)
                     .isEqualTo(model.carrierNetworkChange)
                 assertThat(connectionInfo.isRoaming).isEqualTo(model.roaming)
-                assertThat(conn.networkName.value).isEqualTo(NetworkNameModel.Derived(model.name))
+                assertThat(conn.networkName.value)
+                    .isEqualTo(NetworkNameModel.IntentDerived(model.name))
 
                 // TODO(b/261029387) check these once we start handling them
                 assertThat(connectionInfo.isEmergencyOnly).isFalse()
@@ -594,9 +595,11 @@
     subId: Int = 1,
     level: Int = 1,
     numberOfLevels: Int = 4,
+    activity: Int = DATA_ACTIVITY_NONE,
 ): FakeWifiEventModel.CarrierMerged =
     FakeWifiEventModel.CarrierMerged(
         subscriptionId = subId,
         level = level,
         numberOfLevels = numberOfLevels,
+        activity = activity,
     )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt
index ea90150..abb4561 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod
 
+import android.telephony.TelephonyManager
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -27,6 +28,7 @@
 import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
 import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
+import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.launchIn
@@ -49,6 +51,7 @@
 
     private lateinit var wifiRepository: FakeWifiRepository
     @Mock private lateinit var logger: TableLogBuffer
+    @Mock private lateinit var telephonyManager: TelephonyManager
 
     private val testDispatcher = UnconfinedTestDispatcher()
     private val testScope = TestScope(testDispatcher)
@@ -56,13 +59,16 @@
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
+        whenever(telephonyManager.subscriptionId).thenReturn(SUB_ID)
+        whenever(telephonyManager.simOperatorName).thenReturn("")
+
         wifiRepository = FakeWifiRepository()
 
         underTest =
             CarrierMergedConnectionRepository(
                 SUB_ID,
                 logger,
-                NetworkNameModel.Default("name"),
+                telephonyManager,
                 testScope.backgroundScope,
                 wifiRepository,
             )
@@ -135,6 +141,44 @@
         }
 
     @Test
+    fun connectionInfo_activity_comesFromWifiActivity() =
+        testScope.runTest {
+            var latest: MobileConnectionModel? = null
+            val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+
+            wifiRepository.setIsWifiEnabled(true)
+            wifiRepository.setIsWifiDefault(true)
+            wifiRepository.setWifiNetwork(
+                WifiNetworkModel.CarrierMerged(
+                    networkId = NET_ID,
+                    subscriptionId = SUB_ID,
+                    level = 3,
+                )
+            )
+            wifiRepository.setWifiActivity(
+                DataActivityModel(
+                    hasActivityIn = true,
+                    hasActivityOut = false,
+                )
+            )
+
+            assertThat(latest!!.dataActivityDirection.hasActivityIn).isTrue()
+            assertThat(latest!!.dataActivityDirection.hasActivityOut).isFalse()
+
+            wifiRepository.setWifiActivity(
+                DataActivityModel(
+                    hasActivityIn = false,
+                    hasActivityOut = true,
+                )
+            )
+
+            assertThat(latest!!.dataActivityDirection.hasActivityIn).isFalse()
+            assertThat(latest!!.dataActivityDirection.hasActivityOut).isTrue()
+
+            job.cancel()
+        }
+
+    @Test
     fun connectionInfo_carrierMergedWifi_wrongSubId_isDefault() =
         testScope.runTest {
             var latest: MobileConnectionModel? = null
@@ -244,6 +288,43 @@
             job.cancel()
         }
 
+    @Test
+    fun networkName_usesSimOperatorNameAsInitial() =
+        testScope.runTest {
+            whenever(telephonyManager.simOperatorName).thenReturn("Test SIM name")
+
+            var latest: NetworkNameModel? = null
+            val job = underTest.networkName.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isEqualTo(NetworkNameModel.SimDerived("Test SIM name"))
+
+            job.cancel()
+        }
+
+    @Test
+    fun networkName_updatesOnNetworkUpdate() =
+        testScope.runTest {
+            whenever(telephonyManager.simOperatorName).thenReturn("Test SIM name")
+
+            var latest: NetworkNameModel? = null
+            val job = underTest.networkName.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isEqualTo(NetworkNameModel.SimDerived("Test SIM name"))
+
+            whenever(telephonyManager.simOperatorName).thenReturn("New SIM name")
+            wifiRepository.setWifiNetwork(
+                WifiNetworkModel.CarrierMerged(
+                    networkId = NET_ID,
+                    subscriptionId = SUB_ID,
+                    level = 3,
+                )
+            )
+
+            assertThat(latest).isEqualTo(NetworkNameModel.SimDerived("New SIM name"))
+
+            job.cancel()
+        }
+
     private companion object {
         const val SUB_ID = 123
         const val NET_ID = 456
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
index 24b9f7d..c02ca01 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
@@ -16,20 +16,32 @@
 
 package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod
 
+import android.telephony.ServiceState
+import android.telephony.SignalStrength
+import android.telephony.TelephonyCallback
+import android.telephony.TelephonyManager
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.log.table.TableLogBufferFactory
 import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_EMERGENCY
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_OPERATOR
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_PRIMARY_LEVEL
 import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileTelephonyHelpers.getTelephonyCallbackForType
+import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
+import java.io.PrintWriter
+import java.io.StringWriter
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
@@ -52,9 +64,10 @@
 class FullMobileConnectionRepositoryTest : SysuiTestCase() {
     private lateinit var underTest: FullMobileConnectionRepository
 
+    private val systemClock = FakeSystemClock()
     private val testDispatcher = UnconfinedTestDispatcher()
     private val testScope = TestScope(testDispatcher)
-    private val tableLogBuffer = mock<TableLogBuffer>()
+    private val tableLogBuffer = TableLogBuffer(maxSize = 100, name = "TestName", systemClock)
     private val mobileFactory = mock<MobileConnectionRepositoryImpl.Factory>()
     private val carrierMergedFactory = mock<CarrierMergedConnectionRepository.Factory>()
 
@@ -75,8 +88,7 @@
                 )
             )
             .thenReturn(mobileRepo)
-        whenever(carrierMergedFactory.build(eq(SUB_ID), any(), eq(DEFAULT_NAME)))
-            .thenReturn(carrierMergedRepo)
+        whenever(carrierMergedFactory.build(eq(SUB_ID), any())).thenReturn(carrierMergedRepo)
     }
 
     @Test
@@ -114,7 +126,7 @@
 
             assertThat(underTest.activeRepo.value).isEqualTo(mobileRepo)
             assertThat(underTest.connectionInfo.value).isEqualTo(mobileConnectionInfo)
-            verify(carrierMergedFactory, never()).build(SUB_ID, tableLogBuffer, DEFAULT_NAME)
+            verify(carrierMergedFactory, never()).build(SUB_ID, tableLogBuffer)
         }
 
     @Test
@@ -347,8 +359,219 @@
                 .isSameInstanceAs(connection1Repeat.tableLogBuffer)
         }
 
-    // TODO(b/238425913): Verify that the logging switches correctly (once the carrier merged repo
-    //   implements logging).
+    @Test
+    fun connectionInfo_logging_notCarrierMerged_getsUpdates() =
+        testScope.runTest {
+            // SETUP: Use real repositories to verify the diffing still works. (See b/267501739.)
+            val telephonyManager =
+                mock<TelephonyManager>().apply { whenever(this.simOperatorName).thenReturn("") }
+            createRealMobileRepo(telephonyManager)
+            createRealCarrierMergedRepo(telephonyManager, FakeWifiRepository())
+
+            initializeRepo(startingIsCarrierMerged = false)
+
+            val job = underTest.connectionInfo.launchIn(this)
+
+            // WHEN we set up some mobile connection info
+            val serviceState = ServiceState()
+            serviceState.setOperatorName("longName", "OpTypical", "1")
+            serviceState.isEmergencyOnly = false
+            getTelephonyCallbackForType<TelephonyCallback.ServiceStateListener>(telephonyManager)
+                .onServiceStateChanged(serviceState)
+
+            // THEN it's logged to the buffer
+            assertThat(dumpBuffer()).contains("$COL_OPERATOR${BUFFER_SEPARATOR}OpTypical")
+            assertThat(dumpBuffer()).contains("$COL_EMERGENCY${BUFFER_SEPARATOR}false")
+
+            // WHEN we update mobile connection info
+            val serviceState2 = ServiceState()
+            serviceState2.setOperatorName("longName", "OpDiff", "1")
+            serviceState2.isEmergencyOnly = true
+            getTelephonyCallbackForType<TelephonyCallback.ServiceStateListener>(telephonyManager)
+                .onServiceStateChanged(serviceState2)
+
+            // THEN the updates are logged
+            assertThat(dumpBuffer()).contains("$COL_OPERATOR${BUFFER_SEPARATOR}OpDiff")
+            assertThat(dumpBuffer()).contains("$COL_EMERGENCY${BUFFER_SEPARATOR}true")
+
+            job.cancel()
+        }
+
+    @Test
+    fun connectionInfo_logging_carrierMerged_getsUpdates() =
+        testScope.runTest {
+            // SETUP: Use real repositories to verify the diffing still works. (See b/267501739.)
+            val telephonyManager =
+                mock<TelephonyManager>().apply { whenever(this.simOperatorName).thenReturn("") }
+            createRealMobileRepo(telephonyManager)
+            val wifiRepository = FakeWifiRepository()
+            createRealCarrierMergedRepo(telephonyManager, wifiRepository)
+
+            initializeRepo(startingIsCarrierMerged = true)
+
+            val job = underTest.connectionInfo.launchIn(this)
+
+            // WHEN we set up carrier merged info
+            val networkId = 2
+            wifiRepository.setWifiNetwork(
+                WifiNetworkModel.CarrierMerged(
+                    networkId,
+                    SUB_ID,
+                    level = 3,
+                )
+            )
+
+            // THEN the carrier merged info is logged
+            assertThat(dumpBuffer()).contains("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}3")
+
+            // WHEN we update the info
+            wifiRepository.setWifiNetwork(
+                WifiNetworkModel.CarrierMerged(
+                    networkId,
+                    SUB_ID,
+                    level = 1,
+                )
+            )
+
+            // THEN the updates are logged
+            assertThat(dumpBuffer()).contains("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}1")
+
+            job.cancel()
+        }
+
+    @Test
+    fun connectionInfo_logging_updatesWhenCarrierMergedUpdates() =
+        testScope.runTest {
+            // SETUP: Use real repositories to verify the diffing still works. (See b/267501739.)
+            val telephonyManager =
+                mock<TelephonyManager>().apply { whenever(this.simOperatorName).thenReturn("") }
+            createRealMobileRepo(telephonyManager)
+
+            val wifiRepository = FakeWifiRepository()
+            createRealCarrierMergedRepo(telephonyManager, wifiRepository)
+
+            initializeRepo(startingIsCarrierMerged = false)
+
+            val job = underTest.connectionInfo.launchIn(this)
+
+            // WHEN we set up some mobile connection info
+            val signalStrength = mock<SignalStrength>()
+            whenever(signalStrength.level).thenReturn(1)
+
+            getTelephonyCallbackForType<TelephonyCallback.SignalStrengthsListener>(telephonyManager)
+                .onSignalStrengthsChanged(signalStrength)
+
+            // THEN it's logged to the buffer
+            assertThat(dumpBuffer()).contains("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}1")
+
+            // WHEN isCarrierMerged is set to true
+            val networkId = 2
+            wifiRepository.setWifiNetwork(
+                WifiNetworkModel.CarrierMerged(
+                    networkId,
+                    SUB_ID,
+                    level = 3,
+                )
+            )
+            underTest.setIsCarrierMerged(true)
+
+            // THEN the carrier merged info is logged
+            assertThat(dumpBuffer()).contains("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}3")
+
+            // WHEN the carrier merge network is updated
+            wifiRepository.setWifiNetwork(
+                WifiNetworkModel.CarrierMerged(
+                    networkId,
+                    SUB_ID,
+                    level = 4,
+                )
+            )
+
+            // THEN the new level is logged
+            assertThat(dumpBuffer()).contains("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}4")
+
+            // WHEN isCarrierMerged is set to false
+            underTest.setIsCarrierMerged(false)
+
+            // THEN the typical info is logged
+            // Note: Since our first logs also had the typical info, we need to search the log
+            // contents for after our carrier merged level log.
+            val fullBuffer = dumpBuffer()
+            val carrierMergedContentIndex = fullBuffer.indexOf("${BUFFER_SEPARATOR}4")
+            val bufferAfterCarrierMerged = fullBuffer.substring(carrierMergedContentIndex)
+            assertThat(bufferAfterCarrierMerged).contains("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}1")
+
+            // WHEN the normal network is updated
+            val newMobileInfo =
+                MobileConnectionModel(
+                    operatorAlphaShort = "Mobile Operator 2",
+                    primaryLevel = 0,
+                )
+            mobileRepo.setConnectionInfo(newMobileInfo)
+
+            // THEN the new level is logged
+            assertThat(dumpBuffer()).contains("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}0")
+
+            job.cancel()
+        }
+
+    @Test
+    fun connectionInfo_logging_doesNotLogUpdatesForNotActiveRepo() =
+        testScope.runTest {
+            // SETUP: Use real repositories to verify the diffing still works. (See b/267501739.)
+            val telephonyManager =
+                mock<TelephonyManager>().apply { whenever(this.simOperatorName).thenReturn("") }
+            createRealMobileRepo(telephonyManager)
+
+            val wifiRepository = FakeWifiRepository()
+            createRealCarrierMergedRepo(telephonyManager, wifiRepository)
+
+            // WHEN isCarrierMerged = false
+            initializeRepo(startingIsCarrierMerged = false)
+
+            val job = underTest.connectionInfo.launchIn(this)
+
+            val signalStrength = mock<SignalStrength>()
+            whenever(signalStrength.level).thenReturn(1)
+            getTelephonyCallbackForType<TelephonyCallback.SignalStrengthsListener>(telephonyManager)
+                .onSignalStrengthsChanged(signalStrength)
+
+            // THEN updates to the carrier merged level aren't logged
+            val networkId = 2
+            wifiRepository.setWifiNetwork(
+                WifiNetworkModel.CarrierMerged(
+                    networkId,
+                    SUB_ID,
+                    level = 4,
+                )
+            )
+            assertThat(dumpBuffer()).doesNotContain("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}4")
+
+            wifiRepository.setWifiNetwork(
+                WifiNetworkModel.CarrierMerged(
+                    networkId,
+                    SUB_ID,
+                    level = 3,
+                )
+            )
+            assertThat(dumpBuffer()).doesNotContain("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}3")
+
+            // WHEN isCarrierMerged is set to true
+            underTest.setIsCarrierMerged(true)
+
+            // THEN updates to the normal level aren't logged
+            whenever(signalStrength.level).thenReturn(5)
+            getTelephonyCallbackForType<TelephonyCallback.SignalStrengthsListener>(telephonyManager)
+                .onSignalStrengthsChanged(signalStrength)
+            assertThat(dumpBuffer()).doesNotContain("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}5")
+
+            whenever(signalStrength.level).thenReturn(6)
+            getTelephonyCallbackForType<TelephonyCallback.SignalStrengthsListener>(telephonyManager)
+                .onSignalStrengthsChanged(signalStrength)
+            assertThat(dumpBuffer()).doesNotContain("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}6")
+
+            job.cancel()
+        }
 
     private fun initializeRepo(startingIsCarrierMerged: Boolean) {
         underTest =
@@ -364,9 +587,68 @@
             )
     }
 
+    private fun createRealMobileRepo(
+        telephonyManager: TelephonyManager,
+    ): MobileConnectionRepositoryImpl {
+        whenever(telephonyManager.subscriptionId).thenReturn(SUB_ID)
+
+        val realRepo =
+            MobileConnectionRepositoryImpl(
+                context,
+                SUB_ID,
+                defaultNetworkName = NetworkNameModel.Default("default"),
+                networkNameSeparator = SEP,
+                telephonyManager,
+                systemUiCarrierConfig = mock(),
+                fakeBroadcastDispatcher,
+                mobileMappingsProxy = mock(),
+                testDispatcher,
+                logger = mock(),
+                tableLogBuffer,
+                testScope.backgroundScope,
+            )
+        whenever(
+                mobileFactory.build(
+                    eq(SUB_ID),
+                    any(),
+                    eq(DEFAULT_NAME),
+                    eq(SEP),
+                )
+            )
+            .thenReturn(realRepo)
+
+        return realRepo
+    }
+
+    private fun createRealCarrierMergedRepo(
+        telephonyManager: TelephonyManager,
+        wifiRepository: FakeWifiRepository,
+    ): CarrierMergedConnectionRepository {
+        wifiRepository.setIsWifiEnabled(true)
+        wifiRepository.setIsWifiDefault(true)
+        val realRepo =
+            CarrierMergedConnectionRepository(
+                SUB_ID,
+                tableLogBuffer,
+                telephonyManager,
+                testScope.backgroundScope,
+                wifiRepository,
+            )
+        whenever(carrierMergedFactory.build(eq(SUB_ID), any())).thenReturn(realRepo)
+
+        return realRepo
+    }
+
+    private fun dumpBuffer(): String {
+        val outputWriter = StringWriter()
+        tableLogBuffer.dump(PrintWriter(outputWriter), arrayOf())
+        return outputWriter.toString()
+    }
+
     private companion object {
         const val SUB_ID = 42
         private val DEFAULT_NAME = NetworkNameModel.Default("default name")
         private const val SEP = "-"
+        private const val BUFFER_SEPARATOR = "|"
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
index 3f36bc1..a294088a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
@@ -24,7 +24,6 @@
 import android.telephony.ServiceState.STATE_IN_SERVICE
 import android.telephony.ServiceState.STATE_OUT_OF_SERVICE
 import android.telephony.SignalStrength
-import android.telephony.SubscriptionInfo
 import android.telephony.TelephonyCallback
 import android.telephony.TelephonyCallback.DataActivityListener
 import android.telephony.TelephonyCallback.ServiceStateListener
@@ -71,7 +70,6 @@
 import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
 import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
 import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
@@ -86,7 +84,6 @@
 import org.junit.Before
 import org.junit.Test
 import org.mockito.Mock
-import org.mockito.Mockito
 import org.mockito.MockitoAnnotations
 
 @Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@@ -558,16 +555,51 @@
         }
 
     @Test
-    fun `network name - broadcast not for this sub id - returns default`() =
+    fun `network name - broadcast not for this sub id - keeps old value`() =
         runBlocking(IMMEDIATE) {
             var latest: NetworkNameModel? = null
             val job = underTest.networkName.onEach { latest = it }.launchIn(this)
 
-            val intent = spnIntent(subId = 101)
-
+            val intent = spnIntent()
             fakeBroadcastDispatcher.registeredReceivers.forEach { receiver ->
                 receiver.onReceive(context, intent)
             }
+            assertThat(latest).isEqualTo(intent.toNetworkNameModel(SEP))
+
+            // WHEN an intent with a different subId is sent
+            val wrongSubIntent = spnIntent(subId = 101)
+
+            fakeBroadcastDispatcher.registeredReceivers.forEach { receiver ->
+                receiver.onReceive(context, wrongSubIntent)
+            }
+
+            // THEN the previous intent's name is still used
+            assertThat(latest).isEqualTo(intent.toNetworkNameModel(SEP))
+
+            job.cancel()
+        }
+
+    @Test
+    fun `network name - broadcast has no data - updates to default`() =
+        runBlocking(IMMEDIATE) {
+            var latest: NetworkNameModel? = null
+            val job = underTest.networkName.onEach { latest = it }.launchIn(this)
+
+            val intent = spnIntent()
+            fakeBroadcastDispatcher.registeredReceivers.forEach { receiver ->
+                receiver.onReceive(context, intent)
+            }
+            assertThat(latest).isEqualTo(intent.toNetworkNameModel(SEP))
+
+            val intentWithoutInfo =
+                spnIntent(
+                    showSpn = false,
+                    showPlmn = false,
+                )
+
+            fakeBroadcastDispatcher.registeredReceivers.forEach { receiver ->
+                receiver.onReceive(context, intentWithoutInfo)
+            }
 
             assertThat(latest).isEqualTo(DEFAULT_NAME)
 
@@ -575,7 +607,7 @@
         }
 
     @Test
-    fun `network name - operatorAlphaShort - tracked`() =
+    fun `operatorAlphaShort - tracked`() =
         runBlocking(IMMEDIATE) {
             var latest: String? = null
 
@@ -670,16 +702,8 @@
             job.cancel()
         }
 
-    private fun getTelephonyCallbacks(): List<TelephonyCallback> {
-        val callbackCaptor = argumentCaptor<TelephonyCallback>()
-        Mockito.verify(telephonyManager).registerTelephonyCallback(any(), callbackCaptor.capture())
-        return callbackCaptor.allValues
-    }
-
     private inline fun <reified T> getTelephonyCallbackForType(): T {
-        val cbs = getTelephonyCallbacks().filterIsInstance<T>()
-        assertThat(cbs.size).isEqualTo(1)
-        return cbs[0]
+        return MobileTelephonyHelpers.getTelephonyCallbackForType(telephonyManager)
     }
 
     /** Convenience constructor for SignalStrength */
@@ -713,8 +737,6 @@
     companion object {
         private val IMMEDIATE = Dispatchers.Main.immediate
         private const val SUB_1_ID = 1
-        private val SUB_1 =
-            mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_1_ID) }
 
         private val DEFAULT_NAME = NetworkNameModel.Default("default name")
         private const val SEP = "-"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
index b73348c..673e559 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
@@ -26,6 +26,7 @@
 import android.telephony.CarrierConfigManager
 import android.telephony.SubscriptionInfo
 import android.telephony.SubscriptionManager
+import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
 import android.telephony.TelephonyCallback
 import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener
 import android.telephony.TelephonyManager
@@ -39,6 +40,7 @@
 import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.CarrierConfigRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Factory.Companion.tableBufferLogName
 import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
@@ -55,6 +57,7 @@
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.runBlocking
@@ -84,6 +87,7 @@
     @Mock private lateinit var subscriptionManager: SubscriptionManager
     @Mock private lateinit var telephonyManager: TelephonyManager
     @Mock private lateinit var logger: ConnectivityPipelineLogger
+    @Mock private lateinit var summaryLogger: TableLogBuffer
     @Mock private lateinit var logBufferFactory: TableLogBufferFactory
 
     private val mobileMappings = FakeMobileMappingsProxy()
@@ -93,6 +97,7 @@
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
+        whenever(telephonyManager.simOperatorName).thenReturn("")
 
         // Set up so the individual connection repositories
         whenever(telephonyManager.createForSubscriptionId(anyInt())).thenAnswer { invocation ->
@@ -140,6 +145,7 @@
             )
         carrierMergedFactory =
             CarrierMergedConnectionRepository.Factory(
+                telephonyManager,
                 scope,
                 wifiRepository,
             )
@@ -157,6 +163,7 @@
                 subscriptionManager,
                 telephonyManager,
                 logger,
+                summaryLogger,
                 mobileMappings,
                 fakeBroadcastDispatcher,
                 context,
@@ -252,10 +259,9 @@
         }
 
     @Test
-    fun testActiveDataSubscriptionId_initialValueIsInvalidId() =
+    fun testActiveDataSubscriptionId_initialValueIsNull() =
         runBlocking(IMMEDIATE) {
-            assertThat(underTest.activeMobileDataSubscriptionId.value)
-                .isEqualTo(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
+            assertThat(underTest.activeMobileDataSubscriptionId.value).isEqualTo(null)
         }
 
     @Test
@@ -274,6 +280,140 @@
         }
 
     @Test
+    fun activeSubId_nullIfInvalidSubIdIsReceived() =
+        runBlocking(IMMEDIATE) {
+            var latest: Int? = null
+
+            val job = underTest.activeMobileDataSubscriptionId.onEach { latest = it }.launchIn(this)
+
+            getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
+                .onActiveDataSubscriptionIdChanged(SUB_2_ID)
+
+            assertThat(latest).isNotNull()
+
+            getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
+                .onActiveDataSubscriptionIdChanged(INVALID_SUBSCRIPTION_ID)
+
+            assertThat(latest).isNull()
+
+            job.cancel()
+        }
+
+    @Test
+    fun activeRepo_initiallyNull() {
+        assertThat(underTest.activeMobileDataRepository.value).isNull()
+    }
+
+    @Test
+    fun activeRepo_updatesWithActiveDataId() =
+        runBlocking(IMMEDIATE) {
+            var latest: MobileConnectionRepository? = null
+            val job = underTest.activeMobileDataRepository.onEach { latest = it }.launchIn(this)
+
+            getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
+                .onActiveDataSubscriptionIdChanged(SUB_2_ID)
+
+            assertThat(latest?.subId).isEqualTo(SUB_2_ID)
+
+            job.cancel()
+        }
+
+    @Test
+    fun activeRepo_nullIfActiveDataSubIdBecomesInvalid() =
+        runBlocking(IMMEDIATE) {
+            var latest: MobileConnectionRepository? = null
+            val job = underTest.activeMobileDataRepository.onEach { latest = it }.launchIn(this)
+
+            getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
+                .onActiveDataSubscriptionIdChanged(SUB_2_ID)
+
+            assertThat(latest).isNotNull()
+
+            getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
+                .onActiveDataSubscriptionIdChanged(INVALID_SUBSCRIPTION_ID)
+
+            assertThat(latest).isNull()
+
+            job.cancel()
+        }
+
+    @Test
+    /** Regression test for b/268146648. */
+    fun activeSubIdIsSetBeforeSubscriptionsAreUpdated_doesNotThrow() =
+        runBlocking(IMMEDIATE) {
+            var activeRepo: MobileConnectionRepository? = null
+            var subscriptions: List<SubscriptionModel>? = null
+
+            val activeRepoJob =
+                underTest.activeMobileDataRepository.onEach { activeRepo = it }.launchIn(this)
+            val subscriptionsJob =
+                underTest.subscriptions.onEach { subscriptions = it }.launchIn(this)
+
+            getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
+                .onActiveDataSubscriptionIdChanged(SUB_2_ID)
+
+            assertThat(subscriptions).isEmpty()
+            assertThat(activeRepo).isNotNull()
+
+            activeRepoJob.cancel()
+            subscriptionsJob.cancel()
+        }
+
+    @Test
+    fun getRepoForSubId_activeDataSubIdIsRequestedBeforeSubscriptionsUpdate() =
+        runBlocking(IMMEDIATE) {
+            var latest: MobileConnectionRepository? = null
+            var subscriptions: List<SubscriptionModel>? = null
+            val activeSubIdJob =
+                underTest.activeMobileDataSubscriptionId
+                    .filterNotNull()
+                    .onEach { latest = underTest.getRepoForSubId(it) }
+                    .launchIn(this)
+            val subscriptionsJob =
+                underTest.subscriptions.onEach { subscriptions = it }.launchIn(this)
+
+            // Active data subscription id is sent, but no subscription change has been posted yet
+            getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
+                .onActiveDataSubscriptionIdChanged(SUB_2_ID)
+
+            // Subscriptions list is empty
+            assertThat(subscriptions).isEmpty()
+            // getRepoForSubId does not throw
+            assertThat(latest).isNotNull()
+
+            activeSubIdJob.cancel()
+            subscriptionsJob.cancel()
+        }
+
+    @Test
+    fun activeDataSentBeforeSubscriptionList_subscriptionReusesActiveDataRepo() =
+        runBlocking(IMMEDIATE) {
+            var activeRepo: MobileConnectionRepository? = null
+            val job = underTest.activeMobileDataRepository.onEach { activeRepo = it }.launchIn(this)
+            val subscriptionsJob = underTest.subscriptions.launchIn(this)
+
+            // GIVEN active repo is updated before the subscription list updates
+            getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
+                .onActiveDataSubscriptionIdChanged(SUB_2_ID)
+
+            assertThat(activeRepo).isNotNull()
+
+            // GIVEN the subscription list is then updated which includes the active data sub id
+            whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+                .thenReturn(listOf(SUB_2))
+            getSubscriptionCallback().onSubscriptionsChanged()
+
+            // WHEN requesting a connection repository for the subscription
+            val newRepo = underTest.getRepoForSubId(SUB_2_ID)
+
+            // THEN the newly request repo has been cached and reused
+            assertThat(activeRepo).isSameInstanceAs(newRepo)
+
+            job.cancel()
+            subscriptionsJob.cancel()
+        }
+
+    @Test
     fun testConnectionRepository_validSubId_isCached() =
         runBlocking(IMMEDIATE) {
             val job = underTest.subscriptions.launchIn(this)
@@ -473,7 +613,7 @@
         }
 
     @Test
-    fun `connection repository - log buffer contains sub id in its name`() =
+    fun connectionRepository_logBufferContainsSubIdInItsName() =
         runBlocking(IMMEDIATE) {
             val job = underTest.subscriptions.launchIn(this)
 
@@ -616,6 +756,7 @@
                     subscriptionManager,
                     telephonyManager,
                     logger,
+                    summaryLogger,
                     mobileMappings,
                     fakeBroadcastDispatcher,
                     context,
@@ -688,7 +829,7 @@
         }
 
     @Test
-    fun `active data change - in same group - emits unit`() =
+    fun activeDataChange_inSameGroup_emitsUnit() =
         runBlocking(IMMEDIATE) {
             var latest: Unit? = null
             val job = underTest.activeSubChangedInGroupEvent.onEach { latest = it }.launchIn(this)
@@ -704,7 +845,7 @@
         }
 
     @Test
-    fun `active data change - not in same group - does not emit`() =
+    fun activeDataChange_notInSameGroup_doesNotEmit() =
         runBlocking(IMMEDIATE) {
             var latest: Unit? = null
             val job = underTest.activeSubChangedInGroupEvent.onEach { latest = it }.launchIn(this)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileTelephonyHelpers.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileTelephonyHelpers.kt
new file mode 100644
index 0000000..621f793
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileTelephonyHelpers.kt
@@ -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.systemui.statusbar.pipeline.mobile.data.repository.prod
+
+import android.telephony.TelephonyCallback
+import android.telephony.TelephonyManager
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.google.common.truth.Truth.assertThat
+import org.mockito.Mockito.verify
+
+/** Helper methods for telephony-related callbacks for mobile tests. */
+object MobileTelephonyHelpers {
+    fun getTelephonyCallbacks(mockTelephonyManager: TelephonyManager): List<TelephonyCallback> {
+        val callbackCaptor = argumentCaptor<TelephonyCallback>()
+        verify(mockTelephonyManager).registerTelephonyCallback(any(), callbackCaptor.capture())
+        return callbackCaptor.allValues
+    }
+
+    inline fun <reified T> getTelephonyCallbackForType(mockTelephonyManager: TelephonyManager): T {
+        val cbs = getTelephonyCallbacks(mockTelephonyManager).filterIsInstance<T>()
+        assertThat(cbs.size).isEqualTo(1)
+        return cbs[0]
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
index 7aeaa48..b645e66 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
@@ -45,7 +45,7 @@
     private val _iconGroup = MutableStateFlow<SignalIcon.MobileIconGroup>(TelephonyIcons.THREE_G)
     override val networkTypeIconGroup = _iconGroup
 
-    override val networkName = MutableStateFlow(NetworkNameModel.Derived("demo mode"))
+    override val networkName = MutableStateFlow(NetworkNameModel.IntentDerived("demo mode"))
 
     private val _isEmergencyOnly = MutableStateFlow(false)
     override val isEmergencyOnly = _isEmergencyOnly
@@ -71,6 +71,8 @@
     private val _numberOfLevels = MutableStateFlow(DEFAULT_NUM_LEVELS)
     override val numberOfLevels = _numberOfLevels
 
+    override val isForceHidden = MutableStateFlow(false)
+
     fun setIconGroup(group: SignalIcon.MobileIconGroup) {
         _iconGroup.value = group
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
index 172755c..2699316 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
@@ -73,6 +73,8 @@
     private val _isUserSetup = MutableStateFlow(true)
     override val isUserSetup = _isUserSetup
 
+    override val isForceHidden = MutableStateFlow(false)
+
     /** Always returns a new fake interactor */
     override fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor {
         return FakeMobileIconInteractor(tableLogBuffer)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
index c42aba5..fa072fc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
@@ -66,6 +66,7 @@
                 mobileIconsInteractor.defaultMobileIconGroup,
                 mobileIconsInteractor.defaultDataSubId,
                 mobileIconsInteractor.isDefaultConnectionFailed,
+                mobileIconsInteractor.isForceHidden,
                 connectionRepository,
             )
     }
@@ -530,7 +531,7 @@
             )
             yield()
 
-            assertThat(latest).isEqualTo(NetworkNameModel.Derived(testOperatorName))
+            assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived(testOperatorName))
 
             // Default network name, operator name is null, uses the default
             connectionRepository.setConnectionInfo(MobileConnectionModel(operatorAlphaShort = null))
@@ -550,6 +551,21 @@
             job.cancel()
         }
 
+    @Test
+    fun isForceHidden_matchesParent() =
+        runBlocking(IMMEDIATE) {
+            var latest: Boolean? = null
+            val job = underTest.isForceHidden.onEach { latest = it }.launchIn(this)
+
+            mobileIconsInteractor.isForceHidden.value = true
+            assertThat(latest).isTrue()
+
+            mobileIconsInteractor.isForceHidden.value = false
+            assertThat(latest).isFalse()
+
+            job.cancel()
+        }
+
     companion object {
         private val IMMEDIATE = Dispatchers.Main.immediate
 
@@ -559,6 +575,6 @@
         private const val SUB_1_ID = 1
 
         private val DEFAULT_NAME = NetworkNameModel.Default("test default name")
-        private val DERIVED_NAME = NetworkNameModel.Derived("test derived name")
+        private val DERIVED_NAME = NetworkNameModel.IntentDerived("test derived name")
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
index bd24922..f8a9783 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
@@ -27,6 +27,8 @@
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository
 import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
+import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
+import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
 import com.android.systemui.util.CarrierConfigTracker
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
@@ -50,6 +52,7 @@
 @SmallTest
 class MobileIconsInteractorTest : SysuiTestCase() {
     private lateinit var underTest: MobileIconsInteractor
+    private lateinit var connectivityRepository: FakeConnectivityRepository
     private lateinit var connectionsRepository: FakeMobileConnectionsRepository
     private val userSetupRepository = FakeUserSetupRepository()
     private val mobileMappingsProxy = FakeMobileMappingsProxy()
@@ -63,6 +66,8 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
+        connectivityRepository = FakeConnectivityRepository()
+
         connectionsRepository = FakeMobileConnectionsRepository(mobileMappingsProxy, tableLogBuffer)
         connectionsRepository.setMobileConnectionRepositoryMap(
             mapOf(
@@ -79,6 +84,8 @@
                 connectionsRepository,
                 carrierConfigTracker,
                 logger = mock(),
+                tableLogger = mock(),
+                connectivityRepository,
                 userSetupRepository,
                 testScope.backgroundScope,
             )
@@ -609,6 +616,32 @@
             job.cancel()
         }
 
+    @Test
+    fun isForceHidden_repoHasMobileHidden_true() =
+        testScope.runTest {
+            var latest: Boolean? = null
+            val job = underTest.isForceHidden.onEach { latest = it }.launchIn(this)
+
+            connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.MOBILE))
+
+            assertThat(latest).isTrue()
+
+            job.cancel()
+        }
+
+    @Test
+    fun isForceHidden_repoDoesNotHaveMobileHidden_false() =
+        testScope.runTest {
+            var latest: Boolean? = null
+            val job = underTest.isForceHidden.onEach { latest = it }.launchIn(this)
+
+            connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.WIFI))
+
+            assertThat(latest).isFalse()
+
+            job.cancel()
+        }
+
     companion object {
         private val tableLogBuffer =
             TableLogBuffer(8, "MobileIconsInteractorTest", FakeSystemClock())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt
index a2c1209..e68a397 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt
@@ -29,12 +29,14 @@
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.statusbar.StatusBarIconView
 import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
+import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconInteractor
 import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.LocationBasedMobileViewModel
 import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModel
 import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.QsMobileIconViewModel
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
-import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -58,31 +60,37 @@
 
     @Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
     @Mock private lateinit var tableLogBuffer: TableLogBuffer
-    @Mock private lateinit var logger: ConnectivityPipelineLogger
     @Mock private lateinit var constants: ConnectivityConstants
+    private lateinit var interactor: FakeMobileIconInteractor
+    private lateinit var airplaneModeRepository: FakeAirplaneModeRepository
+    private lateinit var airplaneModeInteractor: AirplaneModeInteractor
 
+    private lateinit var viewModelCommon: MobileIconViewModel
     private lateinit var viewModel: LocationBasedMobileViewModel
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
+        // This line was necessary to make the onDarkChanged and setStaticDrawableColor tests pass.
+        // But, it maybe *shouldn't* be necessary.
+        whenever(constants.hasDataCapabilities).thenReturn(true)
+
         testableLooper = TestableLooper.get(this)
 
-        val interactor = FakeMobileIconInteractor(tableLogBuffer)
-
-        val viewModelCommon =
-            MobileIconViewModel(
-                subscriptionId = 1,
-                interactor,
-                logger,
-                constants,
-                testScope.backgroundScope,
+        airplaneModeRepository = FakeAirplaneModeRepository()
+        airplaneModeInteractor =
+            AirplaneModeInteractor(
+                airplaneModeRepository,
+                FakeConnectivityRepository(),
             )
-        viewModel = QsMobileIconViewModel(viewModelCommon, statusBarPipelineFlags)
+
+        interactor = FakeMobileIconInteractor(tableLogBuffer)
+        createViewModel()
     }
 
     // Note: The following tests are more like integration tests, since they stand up a full
-    // [WifiViewModel] and test the interactions between the view, view-binder, and view-model.
+    // [MobileIconViewModel] and test the interactions between the view, view-binder, and
+    // view-model.
 
     @Test
     fun setVisibleState_icon_iconShownDotHidden() {
@@ -130,7 +138,25 @@
     }
 
     @Test
-    fun isIconVisible_alwaysTrue() {
+    fun isIconVisible_noData_outputsFalse() {
+        whenever(constants.hasDataCapabilities).thenReturn(false)
+        createViewModel()
+
+        val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel)
+
+        ViewUtils.attachView(view)
+        testableLooper.processAllMessages()
+
+        assertThat(view.isIconVisible).isFalse()
+
+        ViewUtils.detachView(view)
+    }
+
+    @Test
+    fun isIconVisible_hasData_outputsTrue() {
+        whenever(constants.hasDataCapabilities).thenReturn(true)
+        createViewModel()
+
         val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel)
 
         ViewUtils.attachView(view)
@@ -142,6 +168,34 @@
     }
 
     @Test
+    fun isIconVisible_notAirplaneMode_outputsTrue() {
+        airplaneModeRepository.setIsAirplaneMode(false)
+
+        val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel)
+
+        ViewUtils.attachView(view)
+        testableLooper.processAllMessages()
+
+        assertThat(view.isIconVisible).isTrue()
+
+        ViewUtils.detachView(view)
+    }
+
+    @Test
+    fun isIconVisible_airplaneMode_outputsTrue() {
+        airplaneModeRepository.setIsAirplaneMode(true)
+
+        val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel)
+
+        ViewUtils.attachView(view)
+        testableLooper.processAllMessages()
+
+        assertThat(view.isIconVisible).isFalse()
+
+        ViewUtils.detachView(view)
+    }
+
+    @Test
     fun onDarkChanged_iconHasNewColor() {
         whenever(statusBarPipelineFlags.useDebugColoring()).thenReturn(false)
         val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel)
@@ -184,6 +238,18 @@
     private fun View.getDotView(): View {
         return this.requireViewById(R.id.status_bar_dot)
     }
+
+    private fun createViewModel() {
+        viewModelCommon =
+            MobileIconViewModel(
+                subscriptionId = 1,
+                interactor,
+                airplaneModeInteractor,
+                constants,
+                testScope.backgroundScope,
+            )
+        viewModel = QsMobileIconViewModel(viewModelCommon, statusBarPipelineFlags)
+    }
 }
 
 private const val SLOT_NAME = "TestSlotName"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
index c960a06..f983030 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
@@ -21,10 +21,13 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
+import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconInteractor
+import com.android.systemui.statusbar.pipeline.mobile.ui.model.SignalIconModel
 import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModelTest.Companion.defaultSignal
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
-import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.launchIn
@@ -46,8 +49,8 @@
     private lateinit var qsIcon: QsMobileIconViewModel
     private lateinit var keyguardIcon: KeyguardMobileIconViewModel
     private lateinit var interactor: FakeMobileIconInteractor
+    private lateinit var airplaneModeInteractor: AirplaneModeInteractor
     @Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
-    @Mock private lateinit var logger: ConnectivityPipelineLogger
     @Mock private lateinit var constants: ConnectivityConstants
     @Mock private lateinit var tableLogBuffer: TableLogBuffer
 
@@ -57,6 +60,11 @@
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
+        airplaneModeInteractor =
+            AirplaneModeInteractor(
+                FakeAirplaneModeRepository(),
+                FakeConnectivityRepository(),
+            )
         interactor = FakeMobileIconInteractor(tableLogBuffer)
         interactor.apply {
             setLevel(1)
@@ -68,7 +76,13 @@
             isDataConnected.value = true
         }
         commonImpl =
-            MobileIconViewModel(SUB_1_ID, interactor, logger, constants, testScope.backgroundScope)
+            MobileIconViewModel(
+                SUB_1_ID,
+                interactor,
+                airplaneModeInteractor,
+                constants,
+                testScope.backgroundScope,
+            )
 
         homeIcon = HomeMobileIconViewModel(commonImpl, statusBarPipelineFlags)
         qsIcon = QsMobileIconViewModel(commonImpl, statusBarPipelineFlags)
@@ -78,14 +92,14 @@
     @Test
     fun `location based view models receive same icon id when common impl updates`() =
         testScope.runTest {
-            var latestHome: Int? = null
-            val homeJob = homeIcon.iconId.onEach { latestHome = it }.launchIn(this)
+            var latestHome: SignalIconModel? = null
+            val homeJob = homeIcon.icon.onEach { latestHome = it }.launchIn(this)
 
-            var latestQs: Int? = null
-            val qsJob = qsIcon.iconId.onEach { latestQs = it }.launchIn(this)
+            var latestQs: SignalIconModel? = null
+            val qsJob = qsIcon.icon.onEach { latestQs = it }.launchIn(this)
 
-            var latestKeyguard: Int? = null
-            val keyguardJob = keyguardIcon.iconId.onEach { latestKeyguard = it }.launchIn(this)
+            var latestKeyguard: SignalIconModel? = null
+            val keyguardJob = keyguardIcon.icon.onEach { latestKeyguard = it }.launchIn(this)
 
             var expected = defaultSignal(level = 1)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
index b91a4df..bec276a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
@@ -19,16 +19,18 @@
 import androidx.test.filters.SmallTest
 import com.android.settingslib.AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH
 import com.android.settingslib.AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH_NONE
-import com.android.settingslib.graph.SignalDrawable
 import com.android.settingslib.mobile.TelephonyIcons.THREE_G
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
+import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconInteractor
+import com.android.systemui.statusbar.pipeline.mobile.ui.model.SignalIconModel
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
-import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
 import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
+import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -49,7 +51,8 @@
 class MobileIconViewModelTest : SysuiTestCase() {
     private lateinit var underTest: MobileIconViewModel
     private lateinit var interactor: FakeMobileIconInteractor
-    @Mock private lateinit var logger: ConnectivityPipelineLogger
+    private lateinit var airplaneModeRepository: FakeAirplaneModeRepository
+    private lateinit var airplaneModeInteractor: AirplaneModeInteractor
     @Mock private lateinit var constants: ConnectivityConstants
     @Mock private lateinit var tableLogBuffer: TableLogBuffer
 
@@ -59,6 +62,15 @@
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
+        whenever(constants.hasDataCapabilities).thenReturn(true)
+
+        airplaneModeRepository = FakeAirplaneModeRepository()
+        airplaneModeInteractor =
+            AirplaneModeInteractor(
+                airplaneModeRepository,
+                FakeConnectivityRepository(),
+            )
+
         interactor = FakeMobileIconInteractor(tableLogBuffer)
         interactor.apply {
             setLevel(1)
@@ -69,15 +81,94 @@
             setNumberOfLevels(4)
             isDataConnected.value = true
         }
-        underTest =
-            MobileIconViewModel(SUB_1_ID, interactor, logger, constants, testScope.backgroundScope)
+        createAndSetViewModel()
     }
 
     @Test
+    fun isVisible_notDataCapable_alwaysFalse() =
+        testScope.runTest {
+            // Create a new view model here so the constants are properly read
+            whenever(constants.hasDataCapabilities).thenReturn(false)
+            createAndSetViewModel()
+
+            var latest: Boolean? = null
+            val job = underTest.isVisible.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isFalse()
+
+            job.cancel()
+        }
+
+    @Test
+    fun isVisible_notAirplane_notForceHidden_true() =
+        testScope.runTest {
+            var latest: Boolean? = null
+            val job = underTest.isVisible.onEach { latest = it }.launchIn(this)
+
+            airplaneModeRepository.setIsAirplaneMode(false)
+            interactor.isForceHidden.value = false
+
+            assertThat(latest).isTrue()
+
+            job.cancel()
+        }
+
+    @Test
+    fun isVisible_airplane_false() =
+        testScope.runTest {
+            var latest: Boolean? = null
+            val job = underTest.isVisible.onEach { latest = it }.launchIn(this)
+
+            airplaneModeRepository.setIsAirplaneMode(true)
+            interactor.isForceHidden.value = false
+
+            assertThat(latest).isFalse()
+
+            job.cancel()
+        }
+
+    @Test
+    fun isVisible_forceHidden_false() =
+        testScope.runTest {
+            var latest: Boolean? = null
+            val job = underTest.isVisible.onEach { latest = it }.launchIn(this)
+
+            airplaneModeRepository.setIsAirplaneMode(false)
+            interactor.isForceHidden.value = true
+
+            assertThat(latest).isFalse()
+
+            job.cancel()
+        }
+
+    @Test
+    fun isVisible_respondsToUpdates() =
+        testScope.runTest {
+            var latest: Boolean? = null
+            val job = underTest.isVisible.onEach { latest = it }.launchIn(this)
+
+            airplaneModeRepository.setIsAirplaneMode(false)
+            interactor.isForceHidden.value = false
+
+            assertThat(latest).isTrue()
+
+            airplaneModeRepository.setIsAirplaneMode(true)
+            assertThat(latest).isFalse()
+
+            airplaneModeRepository.setIsAirplaneMode(false)
+            assertThat(latest).isTrue()
+
+            interactor.isForceHidden.value = true
+            assertThat(latest).isFalse()
+
+            job.cancel()
+        }
+
+    @Test
     fun iconId_correctLevel_notCutout() =
         testScope.runTest {
-            var latest: Int? = null
-            val job = underTest.iconId.onEach { latest = it }.launchIn(this)
+            var latest: SignalIconModel? = null
+            val job = underTest.icon.onEach { latest = it }.launchIn(this)
             val expected = defaultSignal()
 
             assertThat(latest).isEqualTo(expected)
@@ -90,8 +181,8 @@
         testScope.runTest {
             interactor.setIsDefaultDataEnabled(false)
 
-            var latest: Int? = null
-            val job = underTest.iconId.onEach { latest = it }.launchIn(this)
+            var latest: SignalIconModel? = null
+            val job = underTest.icon.onEach { latest = it }.launchIn(this)
             val expected = defaultSignal(level = 1, connected = false)
 
             assertThat(latest).isEqualTo(expected)
@@ -102,8 +193,8 @@
     @Test
     fun `icon - uses empty state - when not in service`() =
         testScope.runTest {
-            var latest: Int? = null
-            val job = underTest.iconId.onEach { latest = it }.launchIn(this)
+            var latest: SignalIconModel? = null
+            val job = underTest.icon.onEach { latest = it }.launchIn(this)
 
             interactor.isInService.value = false
 
@@ -364,14 +455,7 @@
         testScope.runTest {
             // Create a new view model here so the constants are properly read
             whenever(constants.shouldShowActivityConfig).thenReturn(false)
-            underTest =
-                MobileIconViewModel(
-                    SUB_1_ID,
-                    interactor,
-                    logger,
-                    constants,
-                    testScope.backgroundScope,
-                )
+            createAndSetViewModel()
 
             var inVisible: Boolean? = null
             val inJob = underTest.activityInVisible.onEach { inVisible = it }.launchIn(this)
@@ -403,14 +487,7 @@
         testScope.runTest {
             // Create a new view model here so the constants are properly read
             whenever(constants.shouldShowActivityConfig).thenReturn(true)
-            underTest =
-                MobileIconViewModel(
-                    SUB_1_ID,
-                    interactor,
-                    logger,
-                    constants,
-                    testScope.backgroundScope,
-                )
+            createAndSetViewModel()
 
             var inVisible: Boolean? = null
             val inJob = underTest.activityInVisible.onEach { inVisible = it }.launchIn(this)
@@ -459,6 +536,16 @@
             containerJob.cancel()
         }
 
+    private fun createAndSetViewModel() {
+        underTest = MobileIconViewModel(
+            SUB_1_ID,
+            interactor,
+            airplaneModeInteractor,
+            constants,
+            testScope.backgroundScope,
+        )
+    }
+
     companion object {
         private const val SUB_1_ID = 1
 
@@ -466,10 +553,11 @@
         fun defaultSignal(
             level: Int = 1,
             connected: Boolean = true,
-        ): Int {
-            return SignalDrawable.getState(level, /* numLevels */ 4, !connected)
+        ): SignalIconModel {
+            return SignalIconModel(level, numberOfLevels = 4, showExclamationMark = !connected)
         }
 
-        fun emptySignal(): Int = SignalDrawable.getEmptyState(4)
+        fun emptySignal(): SignalIconModel =
+            SignalIconModel(level = 0, numberOfLevels = 4, showExclamationMark = true)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt
index 58b50c7..d9268a2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt
@@ -20,11 +20,14 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.statusbar.phone.StatusBarLocation
 import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
+import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor
 import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
 import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -46,6 +49,7 @@
     private lateinit var underTest: MobileIconsViewModel
     private val interactor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
 
+    private lateinit var airplaneModeInteractor: AirplaneModeInteractor
     @Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
     @Mock private lateinit var logger: ConnectivityPipelineLogger
     @Mock private lateinit var constants: ConnectivityConstants
@@ -57,6 +61,12 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
+        airplaneModeInteractor =
+            AirplaneModeInteractor(
+                FakeAirplaneModeRepository(),
+                FakeConnectivityRepository(),
+            )
+
         val subscriptionIdsFlow =
             interactor.filteredSubscriptions
                 .map { subs -> subs.map { it.subscriptionId } }
@@ -66,6 +76,7 @@
             MobileIconsViewModel(
                 subscriptionIdsFlow,
                 interactor,
+                airplaneModeInteractor,
                 logger,
                 constants,
                 testScope.backgroundScope,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImplTest.kt
index 64a93cf..6557754 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImplTest.kt
@@ -38,6 +38,7 @@
 import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl.Companion.PREFS_CONTROLS_SEEDING_COMPLETED
 import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl.Companion.QS_DEFAULT_POSITION
 import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl.Companion.QS_PRIORITY_POSITION
+import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.settings.SecureSettings
 
 import java.util.Optional
@@ -102,6 +103,8 @@
         `when`(controlsComponent.getControlsListingController())
                 .thenReturn(Optional.of(controlsListingController))
 
+        `when`(controlsComponent.isEnabled()).thenReturn(true)
+
         controller = DeviceControlsControllerImpl(
             mContext,
             controlsComponent,
@@ -168,4 +171,15 @@
         seedCallback.value.accept(SeedResponse(TEST_PKG, true))
         verify(callback).onControlsUpdate(QS_DEFAULT_POSITION)
     }
+
+    @Test
+    fun testControlsDisabledRemoveFromAutoTracker() {
+        `when`(controlsComponent.isEnabled()).thenReturn(false)
+        val callback: DeviceControlsController.Callback = mock()
+
+        controller.setCallback(callback)
+
+        verify(callback).removeControlsAutoTracker()
+        verify(callback, never()).onControlsUpdate(anyInt())
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusManagerTest.kt
index a08e002..56203d9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusManagerTest.kt
@@ -23,6 +23,7 @@
 import android.testing.AndroidTestingRunner
 import android.view.InputDevice
 import androidx.test.filters.SmallTest
+import com.android.internal.logging.UiEventLogger
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
@@ -53,8 +54,11 @@
     @Mock lateinit var bluetoothAdapter: BluetoothAdapter
     @Mock lateinit var bluetoothDevice: BluetoothDevice
     @Mock lateinit var handler: Handler
+
     @Mock lateinit var featureFlags: FeatureFlags
 
+    @Mock lateinit var uiEventLogger: UiEventLogger
+
     @Mock lateinit var stylusCallback: StylusManager.StylusCallback
 
     @Mock lateinit var otherStylusCallback: StylusManager.StylusCallback
@@ -75,7 +79,15 @@
         }
 
         stylusManager =
-            StylusManager(mContext, inputManager, bluetoothAdapter, handler, EXECUTOR, featureFlags)
+            StylusManager(
+                mContext,
+                inputManager,
+                bluetoothAdapter,
+                handler,
+                EXECUTOR,
+                featureFlags,
+                uiEventLogger
+            )
 
         whenever(otherDevice.supportsSource(InputDevice.SOURCE_STYLUS)).thenReturn(false)
         whenever(stylusDevice.supportsSource(InputDevice.SOURCE_STYLUS)).thenReturn(true)
@@ -104,7 +116,15 @@
     @Test
     fun startListener_hasNotStarted_registersInputDeviceListener() {
         stylusManager =
-            StylusManager(mContext, inputManager, bluetoothAdapter, handler, EXECUTOR, featureFlags)
+            StylusManager(
+                mContext,
+                inputManager,
+                bluetoothAdapter,
+                handler,
+                EXECUTOR,
+                featureFlags,
+                uiEventLogger
+            )
 
         stylusManager.startListener()
 
@@ -121,7 +141,15 @@
     @Test
     fun onInputDeviceAdded_hasNotStarted_doesNothing() {
         stylusManager =
-            StylusManager(mContext, inputManager, bluetoothAdapter, handler, EXECUTOR, featureFlags)
+            StylusManager(
+                mContext,
+                inputManager,
+                bluetoothAdapter,
+                handler,
+                EXECUTOR,
+                featureFlags,
+                uiEventLogger
+            )
 
         stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID)
 
@@ -203,7 +231,15 @@
     @Test
     fun onInputDeviceChanged_hasNotStarted_doesNothing() {
         stylusManager =
-            StylusManager(mContext, inputManager, bluetoothAdapter, handler, EXECUTOR, featureFlags)
+            StylusManager(
+                mContext,
+                inputManager,
+                bluetoothAdapter,
+                handler,
+                EXECUTOR,
+                featureFlags,
+                uiEventLogger
+            )
 
         stylusManager.onInputDeviceChanged(STYLUS_DEVICE_ID)
 
@@ -268,7 +304,15 @@
     @Test
     fun onInputDeviceRemoved_hasNotStarted_doesNothing() {
         stylusManager =
-            StylusManager(mContext, inputManager, bluetoothAdapter, handler, EXECUTOR, featureFlags)
+            StylusManager(
+                mContext,
+                inputManager,
+                bluetoothAdapter,
+                handler,
+                EXECUTOR,
+                featureFlags,
+                uiEventLogger
+            )
         stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID)
 
         stylusManager.onInputDeviceRemoved(STYLUS_DEVICE_ID)
@@ -337,6 +381,13 @@
     }
 
     @Test
+    fun onStylusBluetoothConnected_logsEvent() {
+        stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)
+
+        verify(uiEventLogger, times(1)).log(StylusUiEvent.BLUETOOTH_STYLUS_CONNECTED)
+    }
+
+    @Test
     fun onStylusBluetoothDisconnected_unregistersMetadataListener() {
         stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)
 
@@ -346,6 +397,15 @@
     }
 
     @Test
+    fun onStylusBluetoothDisconnected_logsEvent() {
+        stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)
+
+        stylusManager.onInputDeviceRemoved(BT_STYLUS_DEVICE_ID)
+
+        verify(uiEventLogger, times(1)).log(StylusUiEvent.BLUETOOTH_STYLUS_DISCONNECTED)
+    }
+
+    @Test
     fun onMetadataChanged_multipleRegisteredBatteryCallbacks_executesAll() {
         stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)
         stylusManager.registerBatteryCallback(otherStylusBatteryCallback)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerUiTest.kt b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerUiTest.kt
index e1668e8..d51c514 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerUiTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerUiTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.stylus
 
+import android.app.ActivityManager
 import android.app.Notification
 import android.content.BroadcastReceiver
 import android.content.Context
@@ -27,6 +28,7 @@
 import android.view.InputDevice
 import androidx.core.app.NotificationManagerCompat
 import androidx.test.filters.SmallTest
+import com.android.internal.logging.UiEventLogger
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.util.mockito.any
@@ -54,15 +56,23 @@
 @SmallTest
 class StylusUsiPowerUiTest : SysuiTestCase() {
     @Mock lateinit var notificationManager: NotificationManagerCompat
+
     @Mock lateinit var inputManager: InputManager
+
     @Mock lateinit var handler: Handler
+
     @Mock lateinit var btStylusDevice: InputDevice
+
+    @Mock lateinit var uiEventLogger: UiEventLogger
+
     @Captor lateinit var notificationCaptor: ArgumentCaptor<Notification>
 
     private lateinit var stylusUsiPowerUi: StylusUsiPowerUI
     private lateinit var broadcastReceiver: BroadcastReceiver
     private lateinit var contextSpy: Context
 
+    private val uid = ActivityManager.getCurrentUser()
+
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
@@ -80,7 +90,8 @@
         whenever(btStylusDevice.supportsSource(InputDevice.SOURCE_STYLUS)).thenReturn(true)
         whenever(btStylusDevice.bluetoothAddress).thenReturn("SO:ME:AD:DR:ES")
 
-        stylusUsiPowerUi = StylusUsiPowerUI(contextSpy, notificationManager, inputManager, handler)
+        stylusUsiPowerUi =
+            StylusUsiPowerUI(contextSpy, notificationManager, inputManager, handler, uiEventLogger)
         broadcastReceiver = stylusUsiPowerUi.receiver
     }
 
@@ -197,6 +208,19 @@
     }
 
     @Test
+    fun updateBatteryState_showsNotification_logsNotificationShown() {
+        stylusUsiPowerUi.updateBatteryState(0, FixedCapacityBatteryState(0.1f))
+
+        verify(uiEventLogger, times(1))
+            .logWithPosition(
+                StylusUiEvent.STYLUS_LOW_BATTERY_NOTIFICATION_SHOWN,
+                uid,
+                contextSpy.packageName,
+                10
+            )
+    }
+
+    @Test
     fun broadcastReceiver_clicked_hasInputDeviceId_startsUsiDetailsActivity() {
         val intent = Intent(StylusUsiPowerUI.ACTION_CLICKED_LOW_BATTERY)
         val activityIntentCaptor = argumentCaptor<Intent>()
@@ -219,4 +243,32 @@
 
         verify(contextSpy, never()).startActivity(any())
     }
+
+    @Test
+    fun broadcastReceiver_clicked_logsNotificationClicked() {
+        val intent = Intent(StylusUsiPowerUI.ACTION_CLICKED_LOW_BATTERY)
+        broadcastReceiver.onReceive(contextSpy, intent)
+
+        verify(uiEventLogger, times(1))
+            .logWithPosition(
+                StylusUiEvent.STYLUS_LOW_BATTERY_NOTIFICATION_CLICKED,
+                uid,
+                contextSpy.packageName,
+                100
+            )
+    }
+
+    @Test
+    fun broadcastReceiver_dismissed_logsNotificationDismissed() {
+        val intent = Intent(StylusUsiPowerUI.ACTION_DISMISSED_LOW_BATTERY)
+        broadcastReceiver.onReceive(contextSpy, intent)
+
+        verify(uiEventLogger, times(1))
+            .logWithPosition(
+                StylusUiEvent.STYLUS_LOW_BATTERY_NOTIFICATION_DISMISSED,
+                uid,
+                contextSpy.packageName,
+                100
+            )
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java
index 3032ff1f..83439f0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java
@@ -17,6 +17,7 @@
 
 import static com.android.systemui.theme.ThemeOverlayApplier.ANDROID_PACKAGE;
 import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ACCENT_COLOR;
+import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_DYNAMIC_COLOR;
 import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_FONT;
 import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ICON_ANDROID;
 import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ICON_LAUNCHER;
@@ -113,6 +114,8 @@
         };
         when(mOverlayManager.getOverlayInfosForTarget(ANDROID_PACKAGE, UserHandle.SYSTEM))
                 .thenReturn(Lists.newArrayList(
+                        createOverlayInfo(TEST_DISABLED_PREFIX + OVERLAY_CATEGORY_DYNAMIC_COLOR,
+                                ANDROID_PACKAGE, OVERLAY_CATEGORY_DYNAMIC_COLOR, false),
                         createOverlayInfo(TEST_DISABLED_PREFIX + OVERLAY_CATEGORY_ACCENT_COLOR,
                                 ANDROID_PACKAGE, OVERLAY_CATEGORY_ACCENT_COLOR, false),
                         createOverlayInfo(TEST_DISABLED_PREFIX + OVERLAY_CATEGORY_SYSTEM_PALETTE,
@@ -123,6 +126,8 @@
                                 ANDROID_PACKAGE, OVERLAY_CATEGORY_SHAPE, false),
                         createOverlayInfo(TEST_DISABLED_PREFIX + OVERLAY_CATEGORY_ICON_ANDROID,
                                 ANDROID_PACKAGE, OVERLAY_CATEGORY_ICON_ANDROID, false),
+                        createOverlayInfo(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_DYNAMIC_COLOR,
+                                ANDROID_PACKAGE, OVERLAY_CATEGORY_DYNAMIC_COLOR, true),
                         createOverlayInfo(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_ACCENT_COLOR,
                                 ANDROID_PACKAGE, OVERLAY_CATEGORY_ACCENT_COLOR, true),
                         createOverlayInfo(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_SYSTEM_PALETTE,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
index 1710709..f9b5767 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -47,8 +47,9 @@
 import android.os.UserManager;
 import android.provider.Settings;
 import android.testing.AndroidTestingRunner;
+import android.view.accessibility.AccessibilityManager;
 
-import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
@@ -57,7 +58,6 @@
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
-import com.android.systemui.monet.ColorScheme;
 import com.android.systemui.monet.Style;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -115,6 +115,8 @@
     private Resources mResources;
     @Mock
     private WakefulnessLifecycle mWakefulnessLifecycle;
+    @Mock
+    private AccessibilityManager mAccessibilityManager;
     @Captor
     private ArgumentCaptor<BroadcastReceiver> mBroadcastReceiver;
     @Captor
@@ -127,13 +129,13 @@
     private ArgumentCaptor<UserTracker.Callback> mUserTrackerCallback;
     @Captor
     private ArgumentCaptor<ContentObserver> mSettingsObserver;
-    private Style mCurrentStyle;
 
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
         when(mFeatureFlags.isEnabled(Flags.MONET)).thenReturn(true);
         when(mWakefulnessLifecycle.getWakefulness()).thenReturn(WAKEFULNESS_AWAKE);
+        when(mAccessibilityManager.getUiContrast()).thenReturn(0.5f);
         when(mDeviceProvisionedController.isCurrentUserSetup()).thenReturn(true);
         when(mResources.getColor(eq(android.R.color.system_accent1_500), any()))
                 .thenReturn(Color.RED);
@@ -148,15 +150,19 @@
         mThemeOverlayController = new ThemeOverlayController(mContext,
                 mBroadcastDispatcher, mBgHandler, mMainExecutor, mBgExecutor, mThemeOverlayApplier,
                 mSecureSettings, mWallpaperManager, mUserManager, mDeviceProvisionedController,
-                mUserTracker, mDumpManager, mFeatureFlags, mResources, mWakefulnessLifecycle) {
-            @Nullable
-            @Override
-            protected FabricatedOverlay getOverlay(int color, int type, Style style) {
+                mUserTracker, mDumpManager, mFeatureFlags, mResources, mWakefulnessLifecycle,
+                mAccessibilityManager) {
+            @VisibleForTesting
+            protected boolean isNightMode() {
+                return false;
+            }
+
+            @VisibleForTesting
+            protected FabricatedOverlay newFabricatedOverlay(String name) {
                 FabricatedOverlay overlay = mock(FabricatedOverlay.class);
                 when(overlay.getIdentifier())
-                        .thenReturn(new OverlayIdentifier(Integer.toHexString(color | 0xff000000)));
-                mCurrentStyle = style;
-                mColorScheme = new ColorScheme(color, false /* nightMode */, style);
+                        .thenReturn(new OverlayIdentifier(
+                                Integer.toHexString(mColorScheme.getSeed() | 0xff000000)));
                 return overlay;
             }
         };
@@ -416,7 +422,7 @@
 
             mSettingsObserver.getValue().onChange(true, null, 0, mUserTracker.getUserId());
 
-            assertThat(mCurrentStyle).isEqualTo(style);
+            assertThat(mThemeOverlayController.mThemeStyle).isEqualTo(style);
         }
     }
 
@@ -432,7 +438,7 @@
 
         mSettingsObserver.getValue().onChange(true, null, 0, mUserTracker.getUserId());
 
-        assertThat(mCurrentStyle).isEqualTo(Style.TONAL_SPOT);
+        assertThat(mThemeOverlayController.mThemeStyle).isEqualTo(Style.TONAL_SPOT);
     }
 
     @Test
@@ -726,17 +732,20 @@
         mThemeOverlayController = new ThemeOverlayController(mContext,
                 mBroadcastDispatcher, mBgHandler, executor, executor, mThemeOverlayApplier,
                 mSecureSettings, mWallpaperManager, mUserManager, mDeviceProvisionedController,
-                mUserTracker, mDumpManager, mFeatureFlags, mResources, mWakefulnessLifecycle) {
-            @Nullable
-            @Override
-            protected FabricatedOverlay getOverlay(int color, int type, Style style) {
+                mUserTracker, mDumpManager, mFeatureFlags, mResources, mWakefulnessLifecycle,
+                mAccessibilityManager) {
+            @VisibleForTesting
+            protected boolean isNightMode() {
+                return false;
+            }
+
+            @VisibleForTesting
+            protected FabricatedOverlay newFabricatedOverlay(String name) {
                 FabricatedOverlay overlay = mock(FabricatedOverlay.class);
                 when(overlay.getIdentifier())
                         .thenReturn(new OverlayIdentifier("com.thebest.livewallpaperapp.ever"));
-                mColorScheme = new ColorScheme(color, false /* nightMode */, style);
                 return overlay;
             }
-
         };
         mThemeOverlayController.start();
 
@@ -763,14 +772,19 @@
         mThemeOverlayController = new ThemeOverlayController(mContext,
                 mBroadcastDispatcher, mBgHandler, executor, executor, mThemeOverlayApplier,
                 mSecureSettings, mWallpaperManager, mUserManager, mDeviceProvisionedController,
-                mUserTracker, mDumpManager, mFeatureFlags, mResources, mWakefulnessLifecycle) {
-            @Nullable
-            @Override
-            protected FabricatedOverlay getOverlay(int color, int type, Style style) {
+                mUserTracker, mDumpManager, mFeatureFlags, mResources, mWakefulnessLifecycle,
+                mAccessibilityManager) {
+            @VisibleForTesting
+            protected boolean isNightMode() {
+                return false;
+            }
+
+            @VisibleForTesting
+            protected FabricatedOverlay newFabricatedOverlay(String name) {
                 FabricatedOverlay overlay = mock(FabricatedOverlay.class);
                 when(overlay.getIdentifier())
-                        .thenReturn(new OverlayIdentifier(Integer.toHexString(color | 0xff000000)));
-                mColorScheme = new ColorScheme(color, false /* nightMode */, style);
+                        .thenReturn(new OverlayIdentifier(
+                                Integer.toHexString(mColorScheme.getSeed() | 0xff000000)));
                 return overlay;
             }
         };
diff --git a/services/Android.bp b/services/Android.bp
index 3f3ba06..f8097ec 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -191,6 +191,10 @@
         "service-sdksandbox.stubs.system_server",
     ],
 
+    vintf_fragments: [
+        "manifest_services.xml",
+    ],
+
     // Uncomment to enable output of certain warnings (deprecated, unchecked)
     //javacflags: ["-Xlint"],
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index c87d1c8..8e7d277 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -229,17 +229,12 @@
     }
 
     void onDisplayAdded(@NonNull Display display) {
-        if (mInstalled) {
-            resetStreamStateForDisplay(display.getDisplayId());
-            enableFeaturesForDisplay(display);
-        }
+        enableFeaturesForDisplayIfInstalled(display);
+
     }
 
     void onDisplayRemoved(int displayId) {
-        if (mInstalled) {
-            disableFeaturesForDisplay(displayId);
-            resetStreamStateForDisplay(displayId);
-        }
+        disableFeaturesForDisplayIfInstalled(displayId);
     }
 
     @Override
@@ -479,6 +474,9 @@
 
         final Context displayContext = mContext.createDisplayContext(display);
         final int displayId = display.getDisplayId();
+        if (mAms.isDisplayProxyed(displayId)) {
+            return;
+        }
         if (!mServiceDetectsGestures.contains(displayId)) {
             mServiceDetectsGestures.put(displayId, false);
         }
@@ -613,6 +611,18 @@
             mEventHandler.remove(displayId);
         }
     }
+    void enableFeaturesForDisplayIfInstalled(Display display) {
+        if (mInstalled) {
+            resetStreamStateForDisplay(display.getDisplayId());
+            enableFeaturesForDisplay(display);
+        }
+    }
+    void disableFeaturesForDisplayIfInstalled(int displayId) {
+        if (mInstalled) {
+            disableFeaturesForDisplay(displayId);
+            resetStreamStateForDisplay(displayId);
+        }
+    }
 
     private void disableDisplayIndependentFeatures() {
         if (mAutoclickController != null) {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 7e4567b..08e63bc 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -28,7 +28,6 @@
 import static android.provider.Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED;
 import static android.provider.Settings.Secure.CONTRAST_LEVEL;
 import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
-import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_MENU_IN_SYSTEM;
 import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
 import static android.view.accessibility.AccessibilityManager.CONTRAST_DEFAULT_VALUE;
 import static android.view.accessibility.AccessibilityManager.CONTRAST_NOT_SET;
@@ -138,6 +137,7 @@
 import com.android.internal.accessibility.AccessibilityShortcutController.LaunchableFrameworkFeatureInfo;
 import com.android.internal.accessibility.dialog.AccessibilityButtonChooserActivity;
 import com.android.internal.accessibility.dialog.AccessibilityShortcutChooserActivity;
+import com.android.internal.accessibility.util.AccessibilityUtils;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.content.PackageMonitor;
@@ -172,7 +172,6 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
-import java.util.Optional;
 import java.util.Set;
 import java.util.function.Consumer;
 import java.util.function.Function;
@@ -218,9 +217,6 @@
     private static final String SET_PIP_ACTION_REPLACEMENT =
             "setPictureInPictureActionReplacingConnection";
 
-    @VisibleForTesting
-    static final String MENU_SERVICE_RELATIVE_CLASS_NAME = ".AccessibilityMenuService";
-
     private static final char COMPONENT_NAME_SEPARATOR = ':';
 
     private static final int OWN_PROCESS_ID = android.os.Process.myPid();
@@ -474,7 +470,7 @@
                 new MagnificationScaleProvider(mContext));
         mMagnificationProcessor = new MagnificationProcessor(mMagnificationController);
         mCaptioningManagerImpl = new CaptioningManagerImpl(mContext);
-        mProxyManager = new ProxyManager(mLock, mA11yWindowManager);
+        mProxyManager = new ProxyManager(mLock, mA11yWindowManager, mContext);
         mFlashNotificationsController = new FlashNotificationsController(mContext);
         init();
     }
@@ -484,6 +480,7 @@
         registerBroadcastReceivers();
         new AccessibilityContentObserver(mMainHandler).register(
                 mContext.getContentResolver());
+        disableAccessibilityMenuToMigrateIfNeeded();
     }
 
     @Override
@@ -855,91 +852,27 @@
     }
 
     /**
-     * Migrates the Accessibility Menu to the version provided by the system build,
-     * if necessary based on presence of the service on the device.
+     * Disables the component returned by
+     * {@link AccessibilityUtils#getAccessibilityMenuComponentToMigrate} so that it does not appear
+     * in Settings or other places that query for installed accessibility services.
+     *
+     * <p>
+     * SettingsProvider is responsible for migrating users off of Menu-outside-system,
+     * which it performs in its initialization before AccessibilityManagerService is started.
+     * </p>
      */
-    @VisibleForTesting
-    void migrateAccessibilityMenuIfNecessaryLocked(AccessibilityUserState userState) {
-        final Set<ComponentName> menuComponentNames = findA11yMenuComponentNamesLocked();
-        final ComponentName menuOutsideSystem = getA11yMenuOutsideSystem(menuComponentNames);
-        final boolean shouldMigrateToMenuInSystem = menuComponentNames.size() == 2
-                && menuComponentNames.contains(ACCESSIBILITY_MENU_IN_SYSTEM)
-                && menuOutsideSystem != null;
-
-        if (!shouldMigrateToMenuInSystem) {
-            if (menuComponentNames.size() == 1) {
-                // If only one Menu package exists then reset its component to the default state.
-                mPackageManager.setComponentEnabledSetting(
-                        menuComponentNames.stream().findFirst().get(),
-                        PackageManager.COMPONENT_ENABLED_STATE_DEFAULT,
-                        PackageManager.DONT_KILL_APP);
-            }
-            return;
+    private void disableAccessibilityMenuToMigrateIfNeeded() {
+        int userId;
+        synchronized (mLock) {
+            userId = mCurrentUserId;
         }
-
-        // Hide Menu-outside-system so that it does not appear in Settings.
-        mPackageManager.setComponentEnabledSetting(
-                menuOutsideSystem,
-                PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
-                PackageManager.DONT_KILL_APP);
-        // Migrate the accessibility shortcuts.
-        migrateA11yMenuInSettingLocked(userState,
-                Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, menuOutsideSystem);
-        migrateA11yMenuInSettingLocked(userState,
-                Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT, menuOutsideSystem);
-        migrateA11yMenuInSettingLocked(userState,
-                Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, menuOutsideSystem);
-        // If Menu-outside-system is currently enabled by the user then automatically
-        // disable it and enable Menu-in-system.
-        migrateA11yMenuInSettingLocked(userState,
-                Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, menuOutsideSystem);
-    }
-
-    /**
-     * Returns all {@link ComponentName}s whose class name ends in {@link
-     * #MENU_SERVICE_RELATIVE_CLASS_NAME}.
-     **/
-    private Set<ComponentName> findA11yMenuComponentNamesLocked() {
-        Set<ComponentName> result = new ArraySet<>();
-        final var flags =
-                PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_DISABLED_COMPONENTS
-                        | PackageManager.MATCH_DIRECT_BOOT_AWARE
-                        | PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
-        for (ResolveInfo resolveInfo : mPackageManager.queryIntentServicesAsUser(
-                new Intent(AccessibilityService.SERVICE_INTERFACE), flags, mCurrentUserId)) {
-            final ComponentName componentName = resolveInfo.serviceInfo.getComponentName();
-            if (componentName.getClassName().endsWith(MENU_SERVICE_RELATIVE_CLASS_NAME)) {
-                result.add(componentName);
-            }
-        }
-        return result;
-    }
-
-    /**
-     * Returns the first {@link ComponentName} in the provided set that is not equal to {@link
-     * AccessibilityManager#ACCESSIBILITY_MENU_IN_SYSTEM}.
-     */
-    private static ComponentName getA11yMenuOutsideSystem(Set<ComponentName> menuComponentNames) {
-        Optional<ComponentName> menuOutsideSystem = menuComponentNames.stream().filter(
-                name -> !name.equals(ACCESSIBILITY_MENU_IN_SYSTEM)).findFirst();
-        if (menuOutsideSystem.isEmpty()) {
-            return null;
-        }
-        return menuOutsideSystem.get();
-    }
-
-    /**
-     * Replaces <code>toRemove</code> with {@link AccessibilityManager#ACCESSIBILITY_MENU_IN_SYSTEM}
-     * in the requested setting, if present already.
-     */
-    private void migrateA11yMenuInSettingLocked(AccessibilityUserState userState, String setting,
-            ComponentName toRemove) {
-        mTempComponentNameSet.clear();
-        readComponentNamesFromSettingLocked(setting, userState.mUserId, mTempComponentNameSet);
-        if (mTempComponentNameSet.contains(toRemove)) {
-            mTempComponentNameSet.remove(toRemove);
-            mTempComponentNameSet.add(ACCESSIBILITY_MENU_IN_SYSTEM);
-            persistComponentNamesToSettingLocked(setting, mTempComponentNameSet, userState.mUserId);
+        final ComponentName menuToMigrate =
+                AccessibilityUtils.getAccessibilityMenuComponentToMigrate(mPackageManager, userId);
+        if (menuToMigrate != null) {
+            mPackageManager.setComponentEnabledSetting(
+                    menuToMigrate,
+                    PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+                    PackageManager.DONT_KILL_APP);
         }
     }
 
@@ -1712,8 +1645,6 @@
             mCurrentUserId = userId;
             AccessibilityUserState userState = getCurrentUserStateLocked();
 
-            migrateAccessibilityMenuIfNecessaryLocked(userState);
-
             readConfigurationForUserStateLocked(userState);
             mSecurityPolicy.onSwitchUserLocked(mCurrentUserId, userState.mEnabledServices);
             // Even if reading did not yield change, we have to update
@@ -2495,6 +2426,7 @@
                     }
                     inputFilter = mInputFilter;
                     setInputFilter = true;
+                    mProxyManager.setAccessibilityInputFilter(mInputFilter);
                 }
                 mInputFilter.setUserAndEnabledFeatures(userState.mUserId, flags);
                 mInputFilter.setCombinedGenericMotionEventSources(
@@ -3885,6 +3817,10 @@
         return mProxyManager.unregisterProxy(displayId);
     }
 
+    boolean isDisplayProxyed(int displayId) {
+        return mProxyManager.isProxyed(displayId);
+    }
+
     @Override public float getUiContrast() {
         if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
             mTraceManager.logTrace(LOG_TAG + ".getUiContrast", FLAGS_ACCESSIBILITY_MANAGER);
@@ -4379,9 +4315,8 @@
         private final Uri mMagnificationFollowTypingUri = Settings.Secure.getUriFor(
                 Settings.Secure.ACCESSIBILITY_MAGNIFICATION_FOLLOW_TYPING_ENABLED);
 
-        // TODO: replace name with Settings Secure Key
         private final Uri mAlwaysOnMagnificationUri = Settings.Secure.getUriFor(
-                "accessibility_magnification_always_on_enabled");
+                Settings.Secure.ACCESSIBILITY_MAGNIFICATION_ALWAYS_ON_ENABLED);
 
         private final Uri mUiContrastUri = Settings.Secure.getUriFor(
                 CONTRAST_LEVEL);
@@ -4615,10 +4550,9 @@
     }
 
     boolean readAlwaysOnMagnificationLocked(AccessibilityUserState userState) {
-        // TODO: replace name const with Settings Secure Key
         final boolean isSettingsAlwaysOnEnabled = Settings.Secure.getIntForUser(
                 mContext.getContentResolver(),
-                "accessibility_magnification_always_on_enabled",
+                Settings.Secure.ACCESSIBILITY_MAGNIFICATION_ALWAYS_ON_ENABLED,
                 0, userState.mUserId) == 1;
         final boolean isAlwaysOnFeatureFlagEnabled = mMagnificationController
                 .isAlwaysOnMagnificationFeatureFlagEnabled();
diff --git a/services/accessibility/java/com/android/server/accessibility/FlashNotificationsController.java b/services/accessibility/java/com/android/server/accessibility/FlashNotificationsController.java
index b1fa5b1..86b5a12 100644
--- a/services/accessibility/java/com/android/server/accessibility/FlashNotificationsController.java
+++ b/services/accessibility/java/com/android/server/accessibility/FlashNotificationsController.java
@@ -253,7 +253,8 @@
         broadcastFilter.addAction(ACTION_FLASH_NOTIFICATION_START_PREVIEW);
         broadcastFilter.addAction(ACTION_FLASH_NOTIFICATION_STOP_PREVIEW);
         mFlashBroadcastReceiver = new FlashBroadcastReceiver();
-        mContext.registerReceiver(mFlashBroadcastReceiver, broadcastFilter);
+        mContext.registerReceiver(
+                mFlashBroadcastReceiver, broadcastFilter, Context.RECEIVER_EXPORTED);
 
         final PowerManager powerManager = mContext.getSystemService(PowerManager.class);
         mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKE_LOCK_TAG);
diff --git a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
index 54cdb04..2530338 100644
--- a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
@@ -19,10 +19,12 @@
 import android.accessibilityservice.IAccessibilityServiceClient;
 import android.content.ComponentName;
 import android.content.Context;
+import android.hardware.display.DisplayManager;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.SparseArray;
+import android.view.Display;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
 
@@ -47,6 +49,8 @@
 
     private final Object mLock;
 
+    private final Context mContext;
+
     // Used to determine if we should notify AccessibilityManager clients of updates.
     // TODO(254545943): Separate this so each display id has its own state. Currently there is no
     // way to identify from AccessibilityManager which proxy state should be returned.
@@ -57,9 +61,12 @@
 
     private AccessibilityWindowManager mA11yWindowManager;
 
-    ProxyManager(Object lock, AccessibilityWindowManager awm) {
+    private AccessibilityInputFilter mA11yInputFilter;
+
+    ProxyManager(Object lock, AccessibilityWindowManager awm, Context context) {
         mLock = lock;
         mA11yWindowManager = awm;
+        mContext = context;
     }
 
     /**
@@ -109,6 +116,9 @@
             connection.mSystemSupport.onClientChangeLocked(true);
         }
 
+        if (mA11yInputFilter != null) {
+            mA11yInputFilter.disableFeaturesForDisplayIfInstalled(displayId);
+        }
         connection.initializeServiceInterface(client);
     }
 
@@ -120,14 +130,25 @@
     }
 
     private boolean clearConnection(int displayId) {
+        boolean removed = false;
         synchronized (mLock) {
             if (mProxyA11yServiceConnections.contains(displayId)) {
                 mProxyA11yServiceConnections.remove(displayId);
-                return true;
+                removed = true;
             }
         }
-        mA11yWindowManager.stopTrackingDisplayProxy(displayId);
-        return false;
+        if (removed) {
+            mA11yWindowManager.stopTrackingDisplayProxy(displayId);
+            if (mA11yInputFilter != null) {
+                final DisplayManager displayManager = (DisplayManager)
+                        mContext.getSystemService(Context.DISPLAY_SERVICE);
+                final Display proxyDisplay = displayManager.getDisplay(displayId);
+                if (proxyDisplay != null) {
+                    mA11yInputFilter.enableFeaturesForDisplayIfInstalled(proxyDisplay);
+                }
+            }
+        }
+        return removed;
     }
 
     /**
@@ -251,4 +272,8 @@
             proxy.notifyClearAccessibilityNodeInfoCache();
         }
     }
+
+    void setAccessibilityInputFilter(AccessibilityInputFilter filter) {
+        mA11yInputFilter = filter;
+    }
 }
\ No newline at end of file
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index 6b61e97..5a7fbc5 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -150,6 +150,12 @@
     @NonNull
     final FrameworkResourcesServiceNameResolver mAugmentedAutofillResolver;
 
+    /**
+     * Object used to set the name of the field classification service.
+     */
+    @NonNull
+    final FrameworkResourcesServiceNameResolver mFieldClassificationResolver;
+
     private final AutoFillUI mUi;
 
     private final LocalLog mRequestsHistory = new LocalLog(20);
@@ -193,6 +199,32 @@
 
     final AugmentedAutofillState mAugmentedAutofillState = new AugmentedAutofillState();
 
+    /**
+     * Lock used to synchronize access to flags.
+     */
+    private final Object mFlagLock = new Object();
+
+    // Flag holders for Autofill PCC classification
+
+    @GuardedBy("mFlagLock")
+    private boolean mPccClassificationEnabled;
+
+    @GuardedBy("mFlagLock")
+    private boolean mPccPreferProviderOverPcc;
+
+    @GuardedBy("mFlagLock")
+    private boolean mPccUseFallbackDetection;
+
+    @GuardedBy("mFlagLock")
+    private String mPccProviderHints;
+
+    // Default flag values for Autofill PCC
+
+    private static final String DEFAULT_PCC_FEATURE_PROVIDER_HINTS = "";
+    private static final boolean DEFAULT_PREFER_PROVIDER_OVER_PCC = true;
+
+    private static final boolean DEFAULT_PCC_USE_FALLBACK = true;
+
     public AutofillManagerService(Context context) {
         super(context,
                 new SecureSettingsServiceNameResolver(context, Settings.Secure.AUTOFILL_SERVICE),
@@ -219,6 +251,15 @@
         mAugmentedAutofillResolver.setOnTemporaryServiceNameChangedCallback(
                 (u, s, t) -> onAugmentedServiceNameChanged(u, s, t));
 
+        mFieldClassificationResolver = new FrameworkResourcesServiceNameResolver(getContext(),
+                com.android.internal.R.string.config_defaultFieldClassificationService);
+        if (sVerbose) {
+            Slog.v(TAG, "Resolving FieldClassificationService to serviceName: "
+                    + mFieldClassificationResolver.readServiceName(0));
+        }
+        mFieldClassificationResolver.setOnTemporaryServiceNameChangedCallback(
+                (u, s, t) -> onFieldClassificationServiceNameChanged(u, s, t));
+
         if (mSupportedSmartSuggestionModes != AutofillManager.FLAG_SMART_SUGGESTION_OFF) {
             final List<UserInfo> users = getSupportedUsers();
             for (int i = 0; i < users.size(); i++) {
@@ -302,6 +343,10 @@
                 case AutofillFeatureFlags.DEVICE_CONFIG_AUTOFILL_SMART_SUGGESTION_SUPPORTED_MODES:
                 case AutofillFeatureFlags.DEVICE_CONFIG_AUGMENTED_SERVICE_IDLE_UNBIND_TIMEOUT:
                 case AutofillFeatureFlags.DEVICE_CONFIG_AUGMENTED_SERVICE_REQUEST_TIMEOUT:
+                case AutofillFeatureFlags.DEVICE_CONFIG_AUTOFILL_PCC_CLASSIFICATION_ENABLED:
+                case AutofillFeatureFlags.DEVICE_CONFIG_AUTOFILL_PCC_FEATURE_PROVIDER_HINTS:
+                case AutofillFeatureFlags.DEVICE_CONFIG_PREFER_PROVIDER_OVER_PCC:
+                case AutofillFeatureFlags.DEVICE_CONFIG_PCC_USE_FALLBACK:
                     setDeviceConfigProperties();
                     break;
                 case AutofillFeatureFlags.DEVICE_CONFIG_AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES:
@@ -328,6 +373,20 @@
         }
     }
 
+    private void onFieldClassificationServiceNameChanged(
+            @UserIdInt int userId, @Nullable String serviceName, boolean isTemporary) {
+        synchronized (mLock) {
+            final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
+            if (service == null) {
+                // If we cannot get the service from the services cache, it will call
+                // updateRemoteAugmentedAutofillService() finally. Skip call this update again.
+                getServiceForUserLocked(userId);
+            } else {
+                service.updateRemoteFieldClassificationService();
+            }
+        }
+    }
+
     @Override // from AbstractMasterSystemService
     protected AutofillManagerServiceImpl newServiceLocked(@UserIdInt int resolvedUserId,
             boolean disabled) {
@@ -579,13 +638,38 @@
                     AutofillFeatureFlags.DEVICE_CONFIG_AUTOFILL_SMART_SUGGESTION_SUPPORTED_MODES,
                     AutofillManager.FLAG_SMART_SUGGESTION_SYSTEM);
             if (verbose) {
-                Slog.v(mTag, "setDeviceConfigProperties(): "
+                Slog.v(mTag, "setDeviceConfigProperties() for AugmentedAutofill: "
                         + "augmentedIdleTimeout=" + mAugmentedServiceIdleUnbindTimeoutMs
                         + ", augmentedRequestTimeout=" + mAugmentedServiceRequestTimeoutMs
                         + ", smartSuggestionMode="
                         + getSmartSuggestionModeToString(mSupportedSmartSuggestionModes));
             }
         }
+        synchronized (mFlagLock) {
+            mPccClassificationEnabled = DeviceConfig.getBoolean(
+                    DeviceConfig.NAMESPACE_AUTOFILL,
+                    AutofillFeatureFlags.DEVICE_CONFIG_AUTOFILL_PCC_CLASSIFICATION_ENABLED,
+                    AutofillFeatureFlags.DEFAULT_AUTOFILL_PCC_CLASSIFICATION_ENABLED);
+            mPccPreferProviderOverPcc = DeviceConfig.getBoolean(
+                    DeviceConfig.NAMESPACE_AUTOFILL,
+                    AutofillFeatureFlags.DEVICE_CONFIG_PREFER_PROVIDER_OVER_PCC,
+                    DEFAULT_PREFER_PROVIDER_OVER_PCC);
+            mPccUseFallbackDetection = DeviceConfig.getBoolean(
+                    DeviceConfig.NAMESPACE_AUTOFILL,
+                    AutofillFeatureFlags.DEVICE_CONFIG_PCC_USE_FALLBACK,
+                    DEFAULT_PCC_USE_FALLBACK);
+            mPccProviderHints = DeviceConfig.getString(
+                    DeviceConfig.NAMESPACE_AUTOFILL,
+                    AutofillFeatureFlags.DEVICE_CONFIG_AUTOFILL_PCC_FEATURE_PROVIDER_HINTS,
+                    DEFAULT_PCC_FEATURE_PROVIDER_HINTS);
+            if (verbose) {
+                Slog.v(mTag, "setDeviceConfigProperties() for PCC: "
+                        + "mPccClassificationEnabled=" + mPccClassificationEnabled
+                        + ", mPccPreferProviderOverPcc=" + mPccPreferProviderOverPcc
+                        + ", mPccUseFallbackDetection=" + mPccUseFallbackDetection
+                        + ", mPccProviderHints=" + mPccProviderHints);
+            }
+        }
     }
 
     private void updateCachedServices() {
@@ -791,6 +875,46 @@
         }
     }
 
+    /**
+     * Whether the Autofill PCC Classification feature is enabled.
+     */
+    public boolean isPccClassificationEnabled() {
+        synchronized (mFlagLock) {
+            return mPccClassificationEnabled;
+        }
+    }
+
+    /**
+     * Whether the Autofill Provider shouldbe preferred over PCC results for selecting datasets.
+     */
+    public boolean preferProviderOverPcc() {
+        synchronized (mFlagLock) {
+            return mPccPreferProviderOverPcc;
+        }
+    }
+
+    /**
+     * Whether to use the fallback for detection.
+     * If true, use data from secondary source if primary not present .
+     * For eg: if we prefer PCC over provider, and PCC detection didn't classify a field, however,
+     * autofill provider did, this flag would decide whether we use that result, and show some
+     * presentation for that particular field.
+     */
+    public boolean shouldUsePccFallback() {
+        synchronized (mFlagLock) {
+            return mPccUseFallbackDetection;
+        }
+    }
+
+    /**
+     * Provides Autofill Hints that would be requested by the service from the Autofill Provider.
+     */
+    public String getPccProviderHints() {
+        synchronized (mFlagLock) {
+            return mPccProviderHints;
+        }
+    }
+
     @Nullable
     @VisibleForTesting
     static Map<String, String[]> getAllowedCompatModePackages(String setting) {
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index cc29109..76e6974 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -162,6 +162,17 @@
     private long mLastPrune = 0;
 
     /**
+     * Reference to the {@link RemoteFieldClassificationService}, is set on demand.
+     */
+    @GuardedBy("mLock")
+    @Nullable
+    private RemoteFieldClassificationService mRemoteFieldClassificationService;
+
+    @GuardedBy("mLock")
+    @Nullable
+    private ServiceInfo mRemoteFieldClassificationServiceInfo;
+
+    /**
      * Reference to the {@link RemoteAugmentedAutofillService}, is set on demand.
      */
     @GuardedBy("mLock")
@@ -1051,10 +1062,11 @@
         }
         pw.print(prefix); pw.print("Default component: "); pw.println(getContext()
                 .getString(R.string.config_defaultAutofillService));
+        pw.println();
 
-        pw.print(prefix); pw.println("mAugmentedAutofillNamer: ");
-        pw.print(prefix2); mMaster.mAugmentedAutofillResolver.dumpShort(pw, mUserId); pw.println();
-
+        pw.print(prefix); pw.println("mAugmentedAutofillName: ");
+        pw.print(prefix2); mMaster.mAugmentedAutofillResolver.dumpShort(pw, mUserId);
+        pw.println();
         if (mRemoteAugmentedAutofillService != null) {
             pw.print(prefix); pw.println("RemoteAugmentedAutofillService: ");
             mRemoteAugmentedAutofillService.dump(prefix2, pw);
@@ -1063,6 +1075,27 @@
             pw.print(prefix); pw.print("RemoteAugmentedAutofillServiceInfo: ");
             pw.println(mRemoteAugmentedAutofillServiceInfo);
         }
+        pw.println();
+
+        pw.print(prefix); pw.println("mFieldClassificationService for system detection");
+        pw.print(prefix2); pw.print("Default component: "); pw.println(getContext()
+                .getString(R.string.config_defaultFieldClassificationService));
+        pw.print(prefix2); mMaster.mFieldClassificationResolver.dumpShort(pw, mUserId);
+        pw.println();
+
+        if (mRemoteFieldClassificationService != null) {
+            pw.print(prefix); pw.println("RemoteFieldClassificationService: ");
+            mRemoteFieldClassificationService.dump(prefix2, pw);
+        } else {
+            pw.print(prefix); pw.println("mRemoteFieldClassificationService: null");
+        }
+        if (mRemoteFieldClassificationServiceInfo != null) {
+            pw.print(prefix); pw.print("RemoteFieldClassificationServiceInfo: ");
+            pw.println(mRemoteFieldClassificationServiceInfo);
+        } else {
+            pw.print(prefix); pw.println("mRemoteFieldClassificationServiceInfo: null");
+        }
+        pw.println();
 
         pw.print(prefix); pw.print("Field classification enabled: ");
             pw.println(isFieldClassificationEnabledLocked());
@@ -1629,6 +1662,95 @@
         }
     }
 
+    @GuardedBy("mLock")
+    @Nullable RemoteFieldClassificationService getRemoteFieldClassificationServiceLocked() {
+        if (mRemoteFieldClassificationService == null) {
+            final String serviceName = mMaster.mFieldClassificationResolver.getServiceName(mUserId);
+            if (serviceName == null) {
+                if (mMaster.verbose) {
+                    Slog.v(TAG, "getRemoteFieldClassificationServiceLocked(): not set");
+                }
+                return null;
+            }
+            if (sVerbose) {
+                Slog.v(TAG, "getRemoteFieldClassificationServiceLocked serviceName: "
+                        + serviceName);
+            }
+            boolean sTemporaryFieldDetectionService =
+                    mMaster.mFieldClassificationResolver.isTemporary(mUserId);
+            final Pair<ServiceInfo, ComponentName> pair = RemoteFieldClassificationService
+                    .getComponentName(serviceName, mUserId, sTemporaryFieldDetectionService);
+            if (pair == null) {
+                Slog.w(TAG, "RemoteFieldClassificationService.getComponentName returned null "
+                        + "with serviceName: " + serviceName);
+                return null;
+            }
+
+            mRemoteFieldClassificationServiceInfo = pair.first;
+            final ComponentName componentName = pair.second;
+            if (sVerbose) {
+                Slog.v(TAG, "getRemoteFieldClassificationServiceLocked(): " + componentName);
+            }
+            final int serviceUid = mRemoteFieldClassificationServiceInfo.applicationInfo.uid;
+            mRemoteFieldClassificationService = new RemoteFieldClassificationService(getContext(),
+                    componentName, serviceUid, mUserId);
+        }
+
+        return mRemoteFieldClassificationService;
+    }
+
+    @GuardedBy("mLock")
+    @Nullable RemoteFieldClassificationService
+            getRemoteFieldClassificationServiceIfCreatedLocked() {
+        return mRemoteFieldClassificationService;
+    }
+
+    /**
+     * Called when the {@link AutofillManagerService#mAugmentedAutofillResolver}
+     * changed (among other places).
+     */
+    void updateRemoteFieldClassificationService() {
+        synchronized (mLock) {
+            if (mRemoteFieldClassificationService != null) {
+                if (sVerbose) {
+                    Slog.v(TAG, "updateRemoteFieldClassificationService(): "
+                            + "destroying old remote service");
+                }
+                mRemoteFieldClassificationService.unbind();
+
+                mRemoteFieldClassificationService = null;
+                mRemoteFieldClassificationServiceInfo = null;
+            }
+
+            final boolean available = isFieldClassificationServiceAvailableLocked();
+            if (sVerbose) Slog.v(TAG, "updateRemoteFieldClassificationService(): " + available);
+
+            if (available) {
+                mRemoteFieldClassificationService = getRemoteFieldClassificationServiceLocked();
+            }
+        }
+    }
+
+    private boolean isFieldClassificationServiceAvailableLocked() {
+        if (mMaster.verbose) {
+            Slog.v(TAG, "isAugmentedAutofillService(): "
+                    + "setupCompleted=" + isSetupCompletedLocked()
+                    + ", disabled=" + isDisabledByUserRestrictionsLocked()
+                    + ", augmentedService="
+                    + mMaster.mAugmentedAutofillResolver.getServiceName(mUserId));
+        }
+        if (!isSetupCompletedLocked() || isDisabledByUserRestrictionsLocked()
+                || mMaster.mAugmentedAutofillResolver.getServiceName(mUserId) == null) {
+            return false;
+        }
+        return true;
+    }
+
+    boolean isRemoteClassificationServiceForUserLocked(int callingUid) {
+        return mRemoteFieldClassificationServiceInfo != null
+                && mRemoteFieldClassificationServiceInfo.applicationInfo.uid == callingUid;
+    }
+
     @Override
     public String toString() {
         return "AutofillManagerServiceImpl: [userId=" + mUserId
diff --git a/services/autofill/java/com/android/server/autofill/RemoteFieldClassificationService.java b/services/autofill/java/com/android/server/autofill/RemoteFieldClassificationService.java
new file mode 100644
index 0000000..99a2291
--- /dev/null
+++ b/services/autofill/java/com/android/server/autofill/RemoteFieldClassificationService.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.autofill;
+
+import static com.android.server.autofill.Helper.sDebug;
+import static com.android.server.autofill.Helper.sVerbose;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.AppGlobals;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.os.ICancellationSignal;
+import android.os.RemoteException;
+import android.service.assist.classification.FieldClassificationRequest;
+import android.service.assist.classification.FieldClassificationResponse;
+import android.service.assist.classification.FieldClassificationService;
+import android.service.assist.classification.IFieldClassificationCallback;
+import android.service.assist.classification.IFieldClassificationService;
+import android.util.Log;
+import android.util.Pair;
+import android.util.Slog;
+
+import com.android.internal.infra.AbstractRemoteService;
+import com.android.internal.infra.ServiceConnector;
+
+/**
+ * Class responsible for connection with the Remote {@link FieldClassificationService}.
+ * This class is instantiated when {@link AutofillManagerServiceImpl} is established.
+ * The connection is supposed to be bounded forever, as such, this class persists beyond
+ * Autofill {@link Session}'s lifecycle. As such, it can't contain information relevant to Session.
+ * This design is completely different from {@link RemoteFillService}.
+ */
+final class RemoteFieldClassificationService
+        extends ServiceConnector.Impl<IFieldClassificationService> {
+
+    private static final String TAG =
+            "Autofill" + RemoteFieldClassificationService.class.getSimpleName();
+
+    // Bind forever.
+    private static final long TIMEOUT_IDLE_UNBIND_MS =
+            AbstractRemoteService.PERMANENT_BOUND_TIMEOUT_MS;
+    private final ComponentName mComponentName;
+
+    public interface FieldClassificationServiceCallbacks {
+        void onClassificationRequestSuccess(@NonNull FieldClassificationResponse response);
+        void onClassificationRequestFailure(int requestId, @Nullable CharSequence message);
+        void onClassificationRequestTimeout(int requestId);
+        void onServiceDied(@NonNull RemoteFieldClassificationService service);
+    }
+
+    RemoteFieldClassificationService(Context context, ComponentName serviceName,
+            int serviceUid, int userId) {
+        super(context,
+                // TODO(b/266379948): Update service
+                new Intent(FieldClassificationService.SERVICE_INTERFACE).setComponent(serviceName),
+                /* bindingFlags= */ 0, userId, IFieldClassificationService.Stub::asInterface);
+        mComponentName = serviceName;
+        if (sDebug) {
+            Slog.d(TAG, "About to connect to serviceName: " + serviceName);
+        }
+        // Bind right away.
+        connect();
+    }
+
+    @Nullable
+    static Pair<ServiceInfo, ComponentName> getComponentName(@NonNull String serviceName,
+            @UserIdInt int userId, boolean isTemporary) {
+        int flags = PackageManager.GET_META_DATA;
+        if (!isTemporary) {
+            flags |= PackageManager.MATCH_SYSTEM_ONLY;
+        }
+
+        final ComponentName serviceComponent;
+        ServiceInfo serviceInfo = null;
+        try {
+            serviceComponent = ComponentName.unflattenFromString(serviceName);
+            serviceInfo = AppGlobals.getPackageManager().getServiceInfo(serviceComponent, flags,
+                    userId);
+            if (serviceInfo == null) {
+                Slog.e(TAG, "Bad service name for flags " + flags + ": " + serviceName);
+                return null;
+            }
+        } catch (Exception e) {
+            Slog.e(TAG, "Error getting service info for '" + serviceName + "': " + e);
+            return null;
+        }
+        return new Pair<>(serviceInfo, serviceComponent);
+    }
+
+    public ComponentName getComponentName() {
+        return mComponentName;
+    }
+
+    @Override // from ServiceConnector.Impl
+    protected void onServiceConnectionStatusChanged(IFieldClassificationService service,
+            boolean connected) {
+        try {
+            if (connected) {
+                service.onConnected(false, false);
+            } else {
+                service.onDisconnected();
+            }
+        } catch (Exception e) {
+            Slog.w(TAG,
+                    "Exception calling onServiceConnectionStatusChanged(" + connected + "): ", e);
+        }
+    }
+
+    @Override // from AbstractRemoteService
+    protected long getAutoDisconnectTimeoutMs() {
+        return TIMEOUT_IDLE_UNBIND_MS;
+    }
+
+    public void onFieldClassificationRequest(@NonNull FieldClassificationRequest request,
+            FieldClassificationServiceCallbacks fieldClassificationServiceCallbacks) {
+
+        if (sVerbose) {
+            Slog.v(TAG, "onFieldClassificationRequest request:" + request);
+        }
+
+        run(
+                (s) ->
+                        s.onFieldClassificationRequest(
+                                request,
+                                new IFieldClassificationCallback.Stub() {
+                                    @Override
+                                    public void onCancellable(ICancellationSignal cancellation) {
+                                        if (sDebug) {
+                                            Log.d(TAG, "onCancellable");
+                                        }
+                                    }
+
+                                    @Override
+                                    public void onSuccess(FieldClassificationResponse response) {
+                                        if (sDebug) {
+                                            Log.d(TAG, "onSuccess Response: " + response);
+                                        }
+                                        fieldClassificationServiceCallbacks
+                                                .onClassificationRequestSuccess(response);
+                                    }
+
+                                    @Override
+                                    public void onFailure() {
+                                        if (sDebug) {
+                                            Log.d(TAG, "onFailure");
+                                        }
+                                    }
+
+                                    @Override
+                                    public boolean isCompleted() throws RemoteException {
+                                        return false;
+                                    }
+
+                                    @Override
+                                    public void cancel() throws RemoteException {}
+                                }));
+    }
+}
diff --git a/services/autofill/java/com/android/server/autofill/RemoteFillService.java b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
index 94872b0..4688658 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteFillService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
@@ -135,6 +135,9 @@
     }
 
     public void onFillRequest(@NonNull FillRequest request) {
+        if (sVerbose) {
+            Slog.v(TAG, "onFillRequest:" + request);
+        }
         AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>();
         AtomicReference<CompletableFuture<FillResponse>> futureRef = new AtomicReference<>();
 
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index a23a58d..b55f16b 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -88,6 +88,8 @@
 import android.os.RemoteCallback;
 import android.os.RemoteException;
 import android.os.SystemClock;
+import android.service.assist.classification.FieldClassificationRequest;
+import android.service.assist.classification.FieldClassificationResponse;
 import android.service.autofill.AutofillFieldClassificationService.Scores;
 import android.service.autofill.AutofillService;
 import android.service.autofill.CompositeUserData;
@@ -117,7 +119,6 @@
 import android.util.SparseArray;
 import android.util.TimeUtils;
 import android.view.KeyEvent;
-import android.view.autofill.AutofillFeatureFlags;
 import android.view.autofill.AutofillId;
 import android.view.autofill.AutofillManager;
 import android.view.autofill.AutofillManager.AutofillCommitReason;
@@ -126,6 +127,7 @@
 import android.view.autofill.IAutoFillManagerClient;
 import android.view.autofill.IAutofillWindowPresenter;
 import android.view.inputmethod.InlineSuggestionsRequest;
+import android.widget.RemoteViews;
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
@@ -165,13 +167,16 @@
  * until the user authenticates or it times out.
  */
 final class Session implements RemoteFillService.FillServiceCallbacks, ViewState.Listener,
-        AutoFillUI.AutoFillUiCallback, ValueFinder {
+        AutoFillUI.AutoFillUiCallback, ValueFinder,
+        RemoteFieldClassificationService.FieldClassificationServiceCallbacks {
     private static final String TAG = "AutofillSession";
 
     private static final String ACTION_DELAYED_FILL =
             "android.service.autofill.action.DELAYED_FILL";
     private static final String EXTRA_REQUEST_ID = "android.service.autofill.extra.REQUEST_ID";
 
+    private static final String PCC_HINTS_DELIMITER = ",";
+
     final Object mLock;
 
     private final AutofillManagerServiceImpl mService;
@@ -185,6 +190,8 @@
 
     private static AtomicInteger sIdCounter = new AtomicInteger(2);
 
+    private static AtomicInteger sIdCounterForPcc = new AtomicInteger(2);
+
     @GuardedBy("mLock")
     private @SessionState int mSessionState = STATE_UNKNOWN;
 
@@ -395,6 +402,8 @@
     @Nullable
     private ClientSuggestionsSession mClientSuggestionsSession;
 
+    private final ClassificationState mClassificationState = new ClassificationState();
+
     // TODO(b/216576510): Share one BroadcastReceiver between all Sessions instead of creating a
     // new one per Session.
     private final BroadcastReceiver mDelayedFillBroadcastReceiver =
@@ -714,36 +723,36 @@
      * Returns empty list if PCC is off or no types available
     */
     private List<String> getTypeHintsForProvider() {
-        if (!AutofillFeatureFlags.isAutofillPccClassificationEnabled()) {
+        if (!mService.getMaster().isPccClassificationEnabled()) {
             return Collections.EMPTY_LIST;
         }
+        final String typeHints = mService.getMaster().getPccProviderHints();
+        if (TextUtils.isEmpty(typeHints)) {
+            return new ArrayList<>();
+        }
 
-        String[] typeHints = AutofillFeatureFlags.getTypeHintsForProvider();
-        return List.copyOf(Set.of(typeHints));
+        return List.of(typeHints.split(PCC_HINTS_DELIMITER));
     }
 
     /**
      * Assist Data Receiver for PCC
      */
     private final class PccAssistDataReceiverImpl extends IAssistDataReceiver.Stub {
-        // TODO: Uncomment lines below after field classification service definition merged
-        // @GuardedBy("mLock")
-        // private FieldClassificationRequest mPendingFieldClassifitacionRequest;
-        // @GuardedBy("mLock")
-        // private FieldClassificationRequest mLastFieldClassifitacionRequest;
 
         @GuardedBy("mLock")
         void maybeRequestFieldClassificationFromServiceLocked() {
-            // TODO: Uncomment lines below after field classification service definition merged
-            // if (mPendingFieldClassifitacionRequest == null) {
-            //     return;
-            // }
-            // mLastFieldClassifitacionRequest = mPendingFieldClassifitacionRequest;
-            //
-            // mRemoteFieldClassificationService.onFieldClassificationRequest(
-            //        mPendingFieldClassifitacionRequest);
-            //
-            // mPendingFieldClassifitacionRequest = null;
+            if (mClassificationState.mPendingFieldClassificationRequest == null) {
+                Log.w(TAG, "Received AssistData without pending classification request");
+                return;
+            }
+
+            RemoteFieldClassificationService remoteFieldClassificationService =
+                    mService.getRemoteFieldClassificationServiceLocked();
+            if (remoteFieldClassificationService != null) {
+                remoteFieldClassificationService.onFieldClassificationRequest(
+                        mClassificationState.mPendingFieldClassificationRequest, Session.this);
+            }
+            mClassificationState.onFieldClassificationRequestSent();
         }
 
         @Override
@@ -790,12 +799,9 @@
                     ids.get(i).setSessionId(Session.this.id);
                 }
 
-                // TODO: Uncomment lines below after field classification service definition merged
-                // FieldClassificationRequest request = new FieldClassificationRequest(structure);
-                //
-                // mPendingFieldClassifitacionRequest = request;
-                //
-                // maybeRequestFieldClassificationFromServiceLocked();
+                mClassificationState.onAssistStructureReceived(structure);
+
+                maybeRequestFieldClassificationFromServiceLocked();
             }
         }
 
@@ -1174,9 +1180,12 @@
         int requestId;
         // TODO(b/158623971): Update this to prevent possible overflow
         do {
-            requestId = sIdCounter.getAndIncrement();
+            requestId = sIdCounterForPcc.getAndIncrement();
         } while (requestId == INVALID_REQUEST_ID);
 
+        if (sVerbose) {
+            Slog.v(TAG, "request id is " + requestId + ", requesting assist structure for pcc");
+        }
         // Call requestAutofilLData
         try {
             final Bundle receiverExtras = new Bundle();
@@ -1184,8 +1193,8 @@
             final long identity = Binder.clearCallingIdentity();
             try {
                 if (!ActivityTaskManager.getService().requestAutofillData(mPccAssistReceiver,
-                    receiverExtras, mActivityToken, flags)) {
-                    Slog.w(TAG, "failed to request autofill data for pcc: " + mActivityToken);
+                        receiverExtras, mActivityToken, flags)) {
+                    Slog.w(TAG, "failed to request autofill data for " + mActivityToken);
                 }
             } finally {
                 Binder.restoreCallingIdentity(identity);
@@ -1383,6 +1392,8 @@
                 return;
             }
 
+            // TODO: Check if this is required. We can still present datasets to the user even if
+            //  traditional field classification is disabled.
             fieldClassificationIds = response.getFieldClassificationIds();
             if (!mSessionFlags.mClientSuggestionsEnabled && fieldClassificationIds != null
                     && !mService.isFieldClassificationEnabledLocked()) {
@@ -1467,11 +1478,231 @@
             }
         }
 
+        // TODO(b/266379948): Ideally wait for PCC request to finish for a while more
+        // (say 100ms) before proceeding further on.
+
         synchronized (mLock) {
+            response = getEffectiveFillResponse(response);
             processResponseLocked(response, null, requestFlags);
         }
     }
 
+    private FillResponse getEffectiveFillResponse(FillResponse response) {
+        // TODO(b/266379948): label dataset source
+        if (!mService.getMaster().isPccClassificationEnabled()) return response;
+        synchronized (mLock) {
+            if (mClassificationState.mState != ClassificationState.STATE_RESPONSE
+                    || mClassificationState.mLastFieldClassificationResponse == null) {
+                return response;
+            }
+            if (!mClassificationState.processResponse()) return response;
+        }
+        boolean preferAutofillProvider = mService.getMaster().preferProviderOverPcc();
+        boolean shouldUseFallback = mService.getMaster().shouldUsePccFallback();
+        if (preferAutofillProvider && !shouldUseFallback) {
+            return response;
+        }
+
+        DatasetComputationContainer autofillProviderContainer = new DatasetComputationContainer();
+        DatasetComputationContainer detectionPccContainer = new DatasetComputationContainer();
+
+        computeDatasetsForProviderAndUpdateContainer(response, autofillProviderContainer);
+        computeDatasetsForPccAndUpdateContainer(response, detectionPccContainer);
+
+        DatasetComputationContainer resultContainer;
+        if (preferAutofillProvider) {
+            resultContainer = autofillProviderContainer;
+            if (shouldUseFallback) {
+                // add PCC datasets that are not detected by provider.
+                addFallbackDatasets(autofillProviderContainer, detectionPccContainer);
+            }
+        } else {
+            resultContainer = detectionPccContainer;
+            if (shouldUseFallback) {
+                // add Provider's datasets that are not detected by PCC.
+                addFallbackDatasets(detectionPccContainer, autofillProviderContainer);
+            }
+        }
+        // Create FillResponse with effectiveDatasets, and all the rest value from the original
+        // response.
+        return FillResponse.shallowCopy(response, new ArrayList<>(resultContainer.mDatasets));
+    }
+
+    /**
+     * A private class to hold & compute datasets to be shown
+     */
+    private static class DatasetComputationContainer {
+        // List of all autofill ids that have a corresponding datasets
+        Set<AutofillId> mAutofillIds = new ArraySet<>();
+        // Set of datasets. Kept separately, to be able to be used directly for composing
+        // FillResponse.
+        Set<Dataset> mDatasets = new ArraySet<>();
+        ArrayMap<AutofillId, Set<Dataset>> mAutofillIdToDatasetMap = new ArrayMap<>();
+    }
+
+    // Adds fallback datasets to the first container.
+    // This function will destruct and modify c2 container.
+    private void addFallbackDatasets(
+            DatasetComputationContainer c1, DatasetComputationContainer c2) {
+        for (AutofillId id : c2.mAutofillIds) {
+            if (!c1.mAutofillIds.contains(id)) {
+
+                // Since c2 could be modified in a previous iteration, it's possible that all
+                // datasets corresponding to it have been evaluated, and it's map no longer has
+                // any more datasets left. Early return in this case.
+                if (c2.mAutofillIdToDatasetMap.get(id).isEmpty()) return;
+
+                // For AutofillId id, do the following
+                // 1. Add all the datasets corresponding to it to c1's dataset, and update c1
+                // properly.
+                // 2. All the datasets that were added should be removed from the other autofill
+                // ids that were in this dataset. This prevents us from revisiting those datasets.
+                // Although we are using Sets, and that'd avoid re-adding them, using this logic
+                // for now to keep safe. TODO(b/266379948): Revisit this logic.
+
+                Set<Dataset> datasets = c2.mAutofillIdToDatasetMap.get(id);
+                Set<Dataset> copyDatasets = new ArraySet<>(datasets);
+                c1.mAutofillIds.add(id);
+                c1.mAutofillIdToDatasetMap.put(id, copyDatasets);
+                c1.mDatasets.addAll(copyDatasets);
+
+                for (Dataset dataset : datasets) {
+                    for (AutofillId currentId : dataset.getFieldIds()) {
+                        if (currentId.equals(id)) continue;
+                        // For this id, we need to remove the dataset from it's map.
+                        c2.mAutofillIdToDatasetMap.get(currentId).remove(dataset);
+                    }
+                }
+            }
+        }
+    }
+
+    private void computeDatasetsForProviderAndUpdateContainer(
+            FillResponse response, DatasetComputationContainer container) {
+        List<Dataset> datasets = response.getDatasets();
+        if (datasets == null) return;
+        ArrayMap<AutofillId, Set<Dataset>> autofillIdToDatasetMap = new ArrayMap<>();
+        Set<Dataset> eligibleDatasets = new ArraySet<>();
+        Set<AutofillId> eligibleAutofillIds = new ArraySet<>();
+        for (Dataset dataset : response.getDatasets()) {
+            if (dataset.getFieldIds() == null) continue;
+            if (dataset.getAutofillDatatypes() != null
+                    && dataset.getAutofillDatatypes().size() > 0) {
+                continue;
+            }
+            eligibleDatasets.add(dataset);
+            for (AutofillId id : dataset.getFieldIds()) {
+                eligibleAutofillIds.add(id);
+                Set<Dataset> datasetForIds = autofillIdToDatasetMap.get(id);
+                if (datasetForIds == null) {
+                    datasetForIds = new ArraySet<>();
+                }
+                datasetForIds.add(dataset);
+                autofillIdToDatasetMap.put(id, datasetForIds);
+            }
+        }
+        container.mAutofillIdToDatasetMap = autofillIdToDatasetMap;
+        container.mDatasets = eligibleDatasets;
+        container.mAutofillIds = eligibleAutofillIds;
+    }
+
+    private void computeDatasetsForPccAndUpdateContainer(
+            FillResponse response, DatasetComputationContainer container) {
+        List<Dataset> datasets = response.getDatasets();
+        if (datasets == null) return;
+
+        synchronized (mLock) {
+            ArrayMap<String, Set<AutofillId>> hintsToAutofillIdMap =
+                    mClassificationState.mHintsToAutofillIdMap;
+
+            ArrayMap<String, Set<AutofillId>> groupHintsToAutofillIdMap =
+                    mClassificationState.mGroupHintsToAutofillIdMap;
+
+            ArrayMap<AutofillId, Set<Dataset>> map = new ArrayMap<>();
+
+            Set<Dataset> eligibleDatasets = new ArraySet<>();
+            Set<AutofillId> eligibleAutofillIds = new ArraySet<>();
+
+            for (int i = 0; i < datasets.size(); i++) {
+                Dataset dataset = datasets.get(i);
+                if (dataset.getAutofillDatatypes() == null) continue;
+                if (dataset.getFieldIds() != null && dataset.getFieldIds().size() > 0) continue;
+
+                ArrayList<AutofillId> fieldIds = new ArrayList<>();
+                ArrayList<AutofillValue> fieldValues = new ArrayList<>();
+                ArrayList<RemoteViews> fieldPresentations = new ArrayList<>();
+                ArrayList<RemoteViews> fieldDialogPresentations = new ArrayList<>();
+                ArrayList<InlinePresentation> fieldInlinePresentations = new ArrayList<>();
+                ArrayList<InlinePresentation> fieldInlineTooltipPresentations = new ArrayList<>();
+                ArrayList<Dataset.DatasetFieldFilter> fieldFilters = new ArrayList<>();
+
+                for (int j = 0; j < dataset.getAutofillDatatypes().size(); j++) {
+                    String hint = dataset.getAutofillDatatypes().get(j);
+
+                    if (hintsToAutofillIdMap.containsKey(hint)) {
+                        ArrayList<AutofillId> tempIds =
+                                new ArrayList<>(hintsToAutofillIdMap.get(hint));
+
+                        for (AutofillId autofillId : tempIds) {
+                            eligibleAutofillIds.add(autofillId);
+                            // For each of the field, copy over values.
+                            fieldIds.add(autofillId);
+                            fieldValues.add(dataset.getFieldValues().get(j));
+                            //  TODO(b/266379948): might need to make it more efficient by not
+                            //  copying over value if it didn't exist. This would require creating
+                            //  a getter for the presentations arraylist.
+                            fieldPresentations.add(dataset.getFieldPresentation(j));
+                            fieldDialogPresentations.add(dataset.getFieldDialogPresentation(j));
+                            fieldInlinePresentations.add(dataset.getFieldInlinePresentation(j));
+                            fieldInlineTooltipPresentations.add(
+                                    dataset.getFieldInlineTooltipPresentation(j));
+                            fieldFilters.add(dataset.getFilter(j));
+                        }
+
+                        Dataset newDataset =
+                                new Dataset(
+                                        fieldIds,
+                                        fieldValues,
+                                        fieldPresentations,
+                                        fieldDialogPresentations,
+                                        fieldInlinePresentations,
+                                        fieldInlineTooltipPresentations,
+                                        fieldFilters,
+                                        new ArrayList<>(),
+                                        dataset.getFieldContent(),
+                                        null,
+                                        null,
+                                        null,
+                                        null,
+                                        dataset.getId(),
+                                        dataset.getAuthentication());
+                        eligibleDatasets.add(newDataset);
+
+                        // Associate this dataset with all the ids that are represented with it.
+                        Set<Dataset> newDatasets;
+                        for (AutofillId autofillId : tempIds) {
+                            if (map.containsKey(autofillId)) {
+                                newDatasets = map.get(autofillId);
+                            } else {
+                                newDatasets = new ArraySet<>();
+                            }
+                            newDatasets.add(newDataset);
+                            map.put(autofillId, newDatasets);
+                        }
+                    }
+                    // TODO(b/266379948):  handle the case:
+                    // groupHintsToAutofillIdMap.containsKey(hint))
+                    // but the autofill id not being applicable to other hints.
+                    // TODO(b/266379948):  also handle the case where there could be more types in
+                    // the dataset, provided by the provider, however, they aren't applicable.
+                }
+            }
+            container.mAutofillIds = eligibleAutofillIds;
+            container.mDatasets = eligibleDatasets;
+            container.mAutofillIdToDatasetMap = map;
+        }
+    }
+
     @GuardedBy("mLock")
     private void processNullResponseOrFallbackLocked(int requestId, int flags) {
         if (!mSessionFlags.mClientSuggestionsEnabled) {
@@ -4576,6 +4807,189 @@
         }
     }
 
+    /**
+     * Class maintaining the state of the requests to
+     * {@link android.service.assist.classification.FieldClassificationService}.
+     */
+    private static final class ClassificationState {
+
+        /**
+         * Initial state indicating that the request for classification hasn't been triggered yet.
+         */
+        private static final int STATE_INITIAL = 1;
+        /**
+         * Assist request has been triggered, but awaiting response.
+         */
+        private static final int STATE_PENDING_ASSIST_REQUEST = 2;
+        /**
+         * Classification request has been triggered, but awaiting response.
+         */
+        private static final int STATE_PENDING_REQUEST = 3;
+        /**
+         * Classification response has been received.
+         */
+        private static final int STATE_RESPONSE = 4;
+        /**
+         * Classification state has been invalidated, and the last response may no longer be valid.
+         * This could occur due to various reasons like views changing their layouts, becoming
+         * visible or invisible, thereby rendering previous response potentially inaccurate or
+         * incomplete.
+         */
+        private static final int STATE_INVALIDATED = 5;
+
+        @IntDef(prefix = { "STATE_" }, value = {
+                STATE_INITIAL,
+                STATE_PENDING_ASSIST_REQUEST,
+                STATE_PENDING_REQUEST,
+                STATE_RESPONSE,
+                STATE_INVALIDATED
+        })
+        @Retention(RetentionPolicy.SOURCE)
+        @interface ClassificationRequestState{}
+
+        @GuardedBy("mLock")
+        private @ClassificationRequestState int mState = STATE_INITIAL;
+
+        @GuardedBy("mLock")
+        private FieldClassificationRequest mPendingFieldClassificationRequest;
+
+        @GuardedBy("mLock")
+        private FieldClassificationResponse mLastFieldClassificationResponse;
+
+        @GuardedBy("mLock")
+        private ArrayMap<AutofillId, Set<String>> mClassificationHintsMap;
+
+        @GuardedBy("mLock")
+        private ArrayMap<AutofillId, Set<String>> mClassificationGroupHintsMap;
+
+        @GuardedBy("mLock")
+        private ArrayMap<AutofillId, Set<String>> mClassificationCombinedHintsMap;
+
+        /**
+         * Typically, there would be a 1:1 mapping. However, in certain cases, we may have a hint
+         * being applicable to many types. An example of this being new/change password forms,
+         * where you need to confirm the passward twice.
+         */
+        @GuardedBy("mLock")
+        private ArrayMap<String, Set<AutofillId>> mHintsToAutofillIdMap;
+
+        /**
+         * Group hints are expected to have a 1:many mapping. For example, different credit card
+         * fields (creditCardNumber, expiry, cvv) will all map to the same group hints.
+         */
+        @GuardedBy("mLock")
+        private ArrayMap<String, Set<AutofillId>> mGroupHintsToAutofillIdMap;
+
+        @GuardedBy("mLock")
+        private String stateToString() {
+            switch (mState) {
+                case STATE_INITIAL:
+                    return "STATE_INITIAL";
+                case STATE_PENDING_ASSIST_REQUEST:
+                    return "STATE_PENDING_ASSIST_REQUEST";
+                case STATE_PENDING_REQUEST:
+                    return "STATE_PENDING_REQUEST";
+                case STATE_RESPONSE:
+                    return "STATE_RESPONSE";
+                case STATE_INVALIDATED:
+                    return "STATE_INVALIDATED";
+                default:
+                    return "UNKNOWN_CLASSIFICATION_STATE_" + mState;
+            }
+        }
+
+        /**
+         * Process the response received.
+         * @return true if the response was processed, false otherwise. If there wasn't any
+         * response, yet this function was called, it would return false.
+         */
+        @GuardedBy("mLock")
+        private boolean processResponse() {
+            if (mClassificationHintsMap != null && !mClassificationHintsMap.isEmpty()) {
+                // Already processed, so return
+                return true;
+            }
+
+            FieldClassificationResponse response = mLastFieldClassificationResponse;
+            if (response == null) return false;
+
+            mClassificationHintsMap = new ArrayMap<>();
+            mClassificationGroupHintsMap = new ArrayMap<>();
+            mHintsToAutofillIdMap = new ArrayMap<>();
+            mGroupHintsToAutofillIdMap = new ArrayMap<>();
+            Set<android.service.assist.classification.FieldClassification> classifications =
+                    response.getClassifications();
+
+            for (android.service.assist.classification.FieldClassification classification :
+                    classifications) {
+                AutofillId id = classification.getAutofillId();
+                Set<String> hintDetections = classification.getHints();
+                Set<String> groupHintsDetections = classification.getGroupHints();
+                ArraySet<String> combinedHints = new ArraySet<>(hintDetections);
+                mClassificationHintsMap.put(id, hintDetections);
+                if (groupHintsDetections != null) {
+                    mClassificationGroupHintsMap.put(id, groupHintsDetections);
+                    combinedHints.addAll(groupHintsDetections);
+                }
+                mClassificationCombinedHintsMap.put(id, combinedHints);
+
+                processDetections(hintDetections, id, mHintsToAutofillIdMap);
+                processDetections(groupHintsDetections, id, mGroupHintsToAutofillIdMap);
+            }
+            return true;
+        }
+
+        @GuardedBy("mLock")
+        private static void processDetections(Set<String> detections, AutofillId id,
+                ArrayMap<String, Set<AutofillId>> currentMap) {
+            for (String detection : detections) {
+                Set<AutofillId> autofillIds;
+                if (currentMap.containsKey(detection)) {
+                    autofillIds = currentMap.get(detection);
+                } else {
+                    autofillIds = new ArraySet<>();
+                }
+                autofillIds.add(id);
+                currentMap.put(detection, autofillIds);
+            }
+        }
+
+        @GuardedBy("mLock")
+        private void invalidateState() {
+            mState = STATE_INVALIDATED;
+        }
+
+        @GuardedBy("mLock")
+        private void updatePendingAssistData() {
+            mState = STATE_PENDING_ASSIST_REQUEST;
+        }
+
+        @GuardedBy("mLock")
+        private void updatePendingRequest() {
+            mState = STATE_PENDING_REQUEST;
+        }
+
+        @GuardedBy("mLock")
+        private void updateResponseReceived(FieldClassificationResponse response) {
+            mState = STATE_RESPONSE;
+            mLastFieldClassificationResponse = response;
+            mPendingFieldClassificationRequest = null;
+            processResponse();
+        }
+
+        @GuardedBy("mLock")
+        private void onAssistStructureReceived(AssistStructure structure) {
+            mState = STATE_PENDING_REQUEST;
+            mPendingFieldClassificationRequest = new FieldClassificationRequest(structure);
+        }
+
+        @GuardedBy("mLock")
+        private void onFieldClassificationRequestSent() {
+            mState = STATE_PENDING_REQUEST;
+            mPendingFieldClassificationRequest = null;
+        }
+    }
+
     @Override
     public String toString() {
         return "Session: [id=" + id + ", component=" + mComponentName
@@ -5085,4 +5499,28 @@
         ServiceInfo serviceInfo = mService.getServiceInfo();
         return serviceInfo == null ? Process.INVALID_UID : serviceInfo.applicationInfo.uid;
     }
+
+    // DetectionServiceCallbacks
+    public void onClassificationRequestSuccess(@Nullable FieldClassificationResponse response) {
+        mClassificationState.updateResponseReceived(response);
+    }
+
+    public void onClassificationRequestFailure(int requestId, @Nullable CharSequence message) {
+
+    }
+
+    public void onClassificationRequestTimeout(int requestId) {
+
+    }
+
+    @Override
+    public void onServiceDied(@NonNull RemoteFieldClassificationService service) {
+        Slog.w(TAG, "removing session because service died");
+        synchronized (mLock) {
+            // TODO(b/266379948)
+            // forceRemoveFromServiceLocked();
+        }
+    }
+    // DetectionServiceCallbacks end
+
 }
diff --git a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
index b68adab..756dcd2 100644
--- a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
@@ -361,7 +361,6 @@
         params.width = WindowManager.LayoutParams.MATCH_PARENT;
         params.accessibilityTitle = context.getString(R.string.autofill_save_accessibility_title);
         params.windowAnimations = R.style.AutofillSaveAnimation;
-        params.setTrustedOverlay();
 
         show();
     }
diff --git a/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java b/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java
index 312ab54..6675568 100644
--- a/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java
+++ b/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java
@@ -45,6 +45,7 @@
     private static final String TAG = "CameraAccessController";
 
     private final Object mLock = new Object();
+    private final Object mObserverLock = new Object();
 
     private final Context mContext;
     private final VirtualDeviceManagerInternal mVirtualDeviceManagerInternal;
@@ -53,7 +54,7 @@
     private final PackageManager mPackageManager;
     private final UserManager mUserManager;
 
-    @GuardedBy("mLock")
+    @GuardedBy("mObserverLock")
     private int mObserverCount = 0;
 
     @GuardedBy("mLock")
@@ -107,7 +108,7 @@
      * Returns the number of observers currently relying on this controller.
      */
     public int getObserverCount() {
-        synchronized (mLock) {
+        synchronized (mObserverLock) {
             return mObserverCount;
         }
     }
@@ -117,7 +118,7 @@
      * already doing so.
      */
     public void startObservingIfNeeded() {
-        synchronized (mLock) {
+        synchronized (mObserverLock) {
             if (mObserverCount == 0) {
                 mCameraManager.registerAvailabilityCallback(mContext.getMainExecutor(), this);
             }
@@ -129,7 +130,7 @@
      * Stop watching for camera access.
      */
     public void stopObservingIfNeeded() {
-        synchronized (mLock) {
+        synchronized (mObserverLock) {
             mObserverCount--;
             if (mObserverCount <= 0) {
                 close();
@@ -169,7 +170,7 @@
 
     @Override
     public void close() {
-        synchronized (mLock) {
+        synchronized (mObserverLock) {
             if (mObserverCount < 0) {
                 Slog.wtf(TAG, "Unexpected negative mObserverCount: " + mObserverCount);
             } else if (mObserverCount > 0) {
diff --git a/services/core/Android.bp b/services/core/Android.bp
index bd072f5..c8caab9 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -146,6 +146,7 @@
     ],
 
     static_libs: [
+        "android.frameworks.location.altitude-V1-java", // AIDL
         "android.hardware.authsecret-V1.0-java",
         "android.hardware.authsecret-V1-java",
         "android.hardware.boot-V1.0-java", // HIDL
diff --git a/services/core/java/android/os/BatteryStatsInternal.java b/services/core/java/android/os/BatteryStatsInternal.java
index 9c2de65..17ef9a2 100644
--- a/services/core/java/android/os/BatteryStatsInternal.java
+++ b/services/core/java/android/os/BatteryStatsInternal.java
@@ -104,8 +104,18 @@
 
     /**
      * Reports any activity that could potentially have caused the CPU to wake up.
-     * Accepts a timestamp to allow the reporter to report it before or after the event.
+     * Accepts a timestamp to allow free ordering between the event and its reporting.
+     * @param subsystem The subsystem this activity should be attributed to.
+     * @param elapsedMillis The time when this activity happened in the elapsed timebase.
+     * @param uids The uid (or uids) that should be blamed for this activity.
      */
     public abstract void noteCpuWakingActivity(@CpuWakeupSubsystem int subsystem,
             long elapsedMillis, @NonNull int... uids);
+
+    /**
+     * Reports a sound trigger recognition event that may have woken up the CPU.
+     * @param elapsedMillis The time when the event happened in the elapsed timebase.
+     * @param uid The uid that requested this trigger.
+     */
+    public abstract void noteWakingSoundTrigger(long elapsedMillis, int uid);
 }
diff --git a/services/core/java/com/android/server/IntentResolver.java b/services/core/java/com/android/server/IntentResolver.java
index a1adc6f4..2a46d86 100644
--- a/services/core/java/com/android/server/IntentResolver.java
+++ b/services/core/java/com/android/server/IntentResolver.java
@@ -77,80 +77,6 @@
         }
     }
 
-    public static boolean filterEquals(IntentFilter f1, IntentFilter f2) {
-        int s1 = f1.countActions();
-        int s2 = f2.countActions();
-        if (s1 != s2) {
-            return false;
-        }
-        for (int i=0; i<s1; i++) {
-            if (!f2.hasAction(f1.getAction(i))) {
-                return false;
-            }
-        }
-        s1 = f1.countCategories();
-        s2 = f2.countCategories();
-        if (s1 != s2) {
-            return false;
-        }
-        for (int i=0; i<s1; i++) {
-            if (!f2.hasCategory(f1.getCategory(i))) {
-                return false;
-            }
-        }
-        s1 = f1.countDataTypes();
-        s2 = f2.countDataTypes();
-        if (s1 != s2) {
-            return false;
-        }
-        for (int i=0; i<s1; i++) {
-            if (!f2.hasExactDataType(f1.getDataType(i))) {
-                return false;
-            }
-        }
-        s1 = f1.countDataSchemes();
-        s2 = f2.countDataSchemes();
-        if (s1 != s2) {
-            return false;
-        }
-        for (int i=0; i<s1; i++) {
-            if (!f2.hasDataScheme(f1.getDataScheme(i))) {
-                return false;
-            }
-        }
-        s1 = f1.countDataAuthorities();
-        s2 = f2.countDataAuthorities();
-        if (s1 != s2) {
-            return false;
-        }
-        for (int i=0; i<s1; i++) {
-            if (!f2.hasDataAuthority(f1.getDataAuthority(i))) {
-                return false;
-            }
-        }
-        s1 = f1.countDataPaths();
-        s2 = f2.countDataPaths();
-        if (s1 != s2) {
-            return false;
-        }
-        for (int i=0; i<s1; i++) {
-            if (!f2.hasDataPath(f1.getDataPath(i))) {
-                return false;
-            }
-        }
-        s1 = f1.countDataSchemeSpecificParts();
-        s2 = f2.countDataSchemeSpecificParts();
-        if (s1 != s2) {
-            return false;
-        }
-        for (int i=0; i<s1; i++) {
-            if (!f2.hasDataSchemeSpecificPart(f1.getDataSchemeSpecificPart(i))) {
-                return false;
-            }
-        }
-        return true;
-    }
-
     /**
      * Returns whether an intent matches the IntentFilter with a pre-resolved type.
      */
@@ -200,7 +126,7 @@
                 if (cur == null) {
                     break;
                 }
-                if (filterEquals(getIntentFilter(cur), matching)) {
+                if (IntentFilter.filterEquals(getIntentFilter(cur), matching)) {
                     if (res == null) {
                         res = new ArrayList<>();
                     }
@@ -225,7 +151,7 @@
         } else {
             ArrayList<F> res = null;
             for (F cur : mFilters) {
-                if (filterEquals(getIntentFilter(cur), matching)) {
+                if (IntentFilter.filterEquals(getIntentFilter(cur), matching)) {
                     if (res == null) {
                         res = new ArrayList<>();
                     }
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 653c5d1..0252492 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -3748,8 +3748,12 @@
                     // Return both read only and write only volumes. When includeSharedProfile is
                     // true, all the volumes of userIdSharingMedia should be returned when queried
                     // from the user it shares media with
+                    // Public Volumes will be also be returned if visible to the
+                    // userIdSharingMedia with.
                     match = vol.isVisibleForUser(userId)
                             || (!vol.isVisible() && includeInvisible && vol.getPath() != null)
+                            || (vol.getType() == VolumeInfo.TYPE_PUBLIC
+                                    && vol.isVisibleForUser(userIdSharingMedia))
                             || (includeSharedProfile && vol.isVisibleForUser(userIdSharingMedia));
                 }
                 if (!match) continue;
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index cfd22e8..a66e598 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -846,7 +846,7 @@
             mCallForwarding[i] =  false;
             mCellIdentity[i] = null;
             mCellInfo.add(i, Collections.EMPTY_LIST);
-            mImsReasonInfo.add(i, null);
+            mImsReasonInfo.add(i, new ImsReasonInfo());
             mSrvccState[i] = TelephonyManager.SRVCC_STATE_HANDOVER_NONE;
             mCallDisconnectCause[i] = DisconnectCause.NOT_VALID;
             mCallPreciseDisconnectCause[i] = PreciseDisconnectCause.NOT_VALID;
@@ -1265,10 +1265,13 @@
                     }
                 }
                 if (events.contains(TelephonyCallback.EVENT_IMS_CALL_DISCONNECT_CAUSE_CHANGED)) {
-                    try {
-                        r.callback.onImsCallDisconnectCauseChanged(mImsReasonInfo.get(r.phoneId));
-                    } catch (RemoteException ex) {
-                        remove(r.binder);
+                    ImsReasonInfo imsReasonInfo = mImsReasonInfo.get(r.phoneId);
+                    if (imsReasonInfo != null) {
+                        try {
+                            r.callback.onImsCallDisconnectCauseChanged(imsReasonInfo);
+                        } catch (RemoteException ex) {
+                            remove(r.binder);
+                        }
                     }
                 }
                 if (events.contains(
@@ -2418,6 +2421,11 @@
         int phoneId = getPhoneIdFromSubId(subId);
         synchronized (mRecords) {
             if (validatePhoneId(phoneId)) {
+                if (imsReasonInfo == null) {
+                    loge("ImsReasonInfo is null, subId=" + subId + ", phoneId=" + phoneId);
+                    mImsReasonInfo.set(phoneId, new ImsReasonInfo());
+                    return;
+                }
                 mImsReasonInfo.set(phoneId, imsReasonInfo);
                 for (Record r : mRecords) {
                     if (r.matchTelephonyCallbackEvent(
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 56d0b59..24ce684 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -180,6 +180,7 @@
 import android.provider.Settings;
 import android.service.voice.HotwordDetectionService;
 import android.service.voice.VisualQueryDetectionService;
+import android.service.wearable.WearableSensingService;
 import android.stats.devicepolicy.DevicePolicyEnums;
 import android.text.TextUtils;
 import android.util.ArrayMap;
@@ -3813,12 +3814,14 @@
                 inSharedIsolatedProcess);
     }
 
-    // TODO(b/265746493): Special case for HotwordDetectionService and
-    // VisualQueryDetectionService. Need a cleaner way to append this seInfo.
+    // TODO(b/265746493): Special case for HotwordDetectionService,
+    // VisualQueryDetectionService and WearableSensingService.
+    // Need a cleaner way to append this seInfo.
     private String generateAdditionalSeInfoFromService(Intent service) {
         if (service != null && service.getAction() != null
                 && (service.getAction().equals(HotwordDetectionService.SERVICE_INTERFACE)
-                || service.getAction().equals(VisualQueryDetectionService.SERVICE_INTERFACE))) {
+                || service.getAction().equals(VisualQueryDetectionService.SERVICE_INTERFACE)
+                || service.getAction().equals(WearableSensingService.SERVICE_INTERFACE))) {
             return ":isolatedComputeApp";
         }
         return "";
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index e1d1f6c..2aeaa2f 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -962,7 +962,7 @@
     private static final String KEY_ENABLE_WAIT_FOR_FINISH_ATTACH_APPLICATION =
             "enable_wait_for_finish_attach_application";
 
-    private static final boolean DEFAULT_ENABLE_WAIT_FOR_FINISH_ATTACH_APPLICATION = true;
+    private static final boolean DEFAULT_ENABLE_WAIT_FOR_FINISH_ATTACH_APPLICATION = false;
 
     /** @see #KEY_ENABLE_WAIT_FOR_FINISH_ATTACH_APPLICATION */
     public volatile boolean mEnableWaitForFinishAttachApplication =
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index bb73877..36f8377 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -2314,7 +2314,8 @@
         mUiContext = null;
         mAppErrors = null;
         mPackageWatchdog = null;
-        mAppOpsService = mInjector.getAppOpsService(null /* file */, null /* handler */);
+        mAppOpsService = mInjector.getAppOpsService(null /* recentAccessesFile */,
+            null /* storageFile */, null /* handler */);
         mBatteryStatsService = mInjector.getBatteryStatsService();
         mHandler = new MainHandler(handlerThread.getLooper());
         mHandlerThread = handlerThread;
@@ -2443,7 +2444,8 @@
 
         mProcessStats = new ProcessStatsService(this, new File(systemDir, "procstats"));
 
-        mAppOpsService = mInjector.getAppOpsService(new File(systemDir, "appops.xml"), mHandler);
+        mAppOpsService = mInjector.getAppOpsService(new File(systemDir, "appops_accesses.xml"),
+                new File(systemDir, "appops.xml"), mHandler);
 
         mUgmInternal = LocalServices.getService(UriGrantsManagerInternal.class);
 
@@ -13282,8 +13284,10 @@
         // restored. This distinction is important for system-process packages that live in the
         // system user's process but backup/restore data for non-system users.
         // TODO (b/123688746): Handle all system-process packages with singleton check.
-        final int instantiatedUserId =
-                PLATFORM_PACKAGE_NAME.equals(packageName) ? UserHandle.USER_SYSTEM : targetUserId;
+        boolean useSystemUser = PLATFORM_PACKAGE_NAME.equals(packageName)
+                || getPackageManagerInternal().getSystemUiServiceComponent().getPackageName()
+                        .equals(packageName);
+        final int instantiatedUserId = useSystemUser ? UserHandle.USER_SYSTEM : targetUserId;
 
         IPackageManager pm = AppGlobals.getPackageManager();
         ApplicationInfo app = null;
@@ -19001,8 +19005,9 @@
             return mContext;
         }
 
-        public AppOpsService getAppOpsService(File file, Handler handler) {
-            return new AppOpsService(file, handler, getContext());
+        public AppOpsService getAppOpsService(File recentAccessesFile, File storageFile,
+                Handler handler) {
+            return new AppOpsService(recentAccessesFile, storageFile, handler, getContext());
         }
 
         public Handler getUiHandler(ActivityManagerService service) {
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index f73594c..19235c9 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -483,6 +483,12 @@
             Objects.requireNonNull(uids);
             mCpuWakeupStats.noteWakingActivity(subsystem, elapsedMillis, uids);
         }
+
+        @Override
+        public void noteWakingSoundTrigger(long elapsedMillis, int uid) {
+            // TODO(b/267717665): Pipe to noteCpuWakingActivity once SoundTrigger starts using this.
+            Slog.w(TAG, "Sound trigger event dispatched to uid " + uid);
+        }
     }
 
     @Override
diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
index 7290f32..c07ef1d 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
@@ -273,7 +273,7 @@
                         performReceiveLocked(oldRecord.resultToApp, oldRecord.resultTo,
                                 oldRecord.intent,
                                 Activity.RESULT_CANCELED, null, null,
-                                false, false, r.shareIdentity, oldRecord.userId,
+                                false, false, oldRecord.shareIdentity, oldRecord.userId,
                                 oldRecord.callingUid, r.callingUid, r.callerPackage,
                                 SystemClock.uptimeMillis() - oldRecord.enqueueTime, 0);
                     } catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index b952ce0..f954420 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -1064,7 +1064,7 @@
         if (thread != null) {
             mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(
                     app, OOM_ADJ_REASON_FINISH_RECEIVER);
-            if (r.shareIdentity) {
+            if (r.shareIdentity && app.uid != r.callingUid) {
                 mService.mPackageManagerInt.grantImplicitAccess(r.userId, r.intent,
                         UserHandle.getAppId(app.uid), r.callingUid, true);
             }
diff --git a/services/core/java/com/android/server/am/BroadcastReceiverBatch.java b/services/core/java/com/android/server/am/BroadcastReceiverBatch.java
index 153403a..63575ba 100644
--- a/services/core/java/com/android/server/am/BroadcastReceiverBatch.java
+++ b/services/core/java/com/android/server/am/BroadcastReceiverBatch.java
@@ -171,8 +171,9 @@
     // Add a ReceiverInfo for a registered receiver.
     void schedule(@Nullable IIntentReceiver receiver, Intent intent,
             int resultCode, @Nullable String data, @Nullable Bundle extras, boolean ordered,
-            boolean sticky, boolean assumeDelivered, int sendingUser, int callingUid,
-            String callingPackage, int processState, @Nullable BroadcastRecord r, int index) {
+            boolean sticky, boolean assumeDelivered, int sendingUser, int sendingUid,
+            @Nullable String sendingPackage, int processState, @Nullable BroadcastRecord r,
+            int index) {
         ReceiverInfo ri = new ReceiverInfo();
         ri.intent = intent;
         ri.data = data;
@@ -185,8 +186,8 @@
         ri.receiver = receiver;
         ri.ordered = ordered;
         ri.sticky = sticky;
-        ri.sentFromUid = callingUid;
-        ri.sentFromPackage = callingPackage;
+        ri.sendingUid = sendingUid;
+        ri.sendingPackage = sendingPackage;
 
         mReceivers.add(ri);
         mCookies.add(cookiePool.next().set(r, index));
@@ -195,7 +196,7 @@
     void schedule(@Nullable Intent intent, @Nullable ActivityInfo activityInfo,
             @Nullable CompatibilityInfo compatInfo, int resultCode, @Nullable String data,
             @Nullable Bundle extras, boolean sync, boolean assumeDelivered, int sendingUser,
-            int callingUid, @Nullable String callingPackage, int processState,
+            int sendingUid, @Nullable String sendingPackage, int processState,
             @Nullable BroadcastRecord r, int index) {
         ReceiverInfo ri = new ReceiverInfo();
         ri.intent = intent;
@@ -209,8 +210,8 @@
         ri.activityInfo = activityInfo;
         ri.compatInfo = compatInfo;
         ri.sync = sync;
-        ri.sentFromUid = callingUid;
-        ri.sentFromPackage = callingPackage;
+        ri.sendingUid = sendingUid;
+        ri.sendingPackage = sendingPackage;
         mReceivers.add(ri);
         mCookies.add(cookiePool.next().set(r, index));
     }
@@ -223,21 +224,21 @@
     ArrayList<ReceiverInfo> registeredReceiver(@Nullable IIntentReceiver receiver,
             @Nullable Intent intent, int resultCode, @Nullable String data,
             @Nullable Bundle extras, boolean ordered, boolean sticky, boolean assumeDelivered,
-            int sendingUser, int callingUid, String callingPackage, int processState) {
+            int sendingUser, int sendingUid, @Nullable String sendingPackage, int processState) {
         reset();
         schedule(receiver, intent, resultCode, data, extras, ordered, sticky, assumeDelivered,
-                sendingUser, callingUid, callingPackage, processState, null, 0);
+                sendingUser, sendingUid, sendingPackage, processState, null, 0);
         return receivers();
     }
 
     ArrayList<ReceiverInfo> manifestReceiver(@Nullable Intent intent,
             @Nullable ActivityInfo activityInfo, @Nullable CompatibilityInfo compatInfo,
             int resultCode, @Nullable String data, @Nullable Bundle extras, boolean sync,
-            boolean assumeDelivered, int sendingUser, int callingUid, String callingPackage,
-            int processState) {
+            boolean assumeDelivered, int sendingUser, int sendingUid,
+            @Nullable String sendingPackage, int processState) {
         reset();
         schedule(intent, activityInfo, compatInfo, resultCode, data, extras, sync, assumeDelivered,
-                sendingUser, callingUid, callingPackage, processState, null, 0);
+                sendingUser, sendingUid, sendingPackage, processState, null, 0);
         return receivers();
     }
 
diff --git a/services/core/java/com/android/server/am/ReceiverList.java b/services/core/java/com/android/server/am/ReceiverList.java
index f3d8ba15..7d2dab6 100644
--- a/services/core/java/com/android/server/am/ReceiverList.java
+++ b/services/core/java/com/android/server/am/ReceiverList.java
@@ -25,8 +25,6 @@
 import android.util.Printer;
 import android.util.proto.ProtoOutputStream;
 
-import com.android.server.IntentResolver;
-
 import java.io.PrintWriter;
 import java.util.ArrayList;
 
@@ -74,7 +72,7 @@
         final int N = size();
         for (int i = 0; i < N; i++) {
             final BroadcastFilter f = get(i);
-            if (IntentResolver.filterEquals(f, filter)) {
+            if (IntentFilter.filterEquals(f, filter)) {
                 return true;
             }
         }
diff --git a/services/core/java/com/android/server/am/SameProcessApplicationThread.java b/services/core/java/com/android/server/am/SameProcessApplicationThread.java
index dcb02ea..82dd5c2 100644
--- a/services/core/java/com/android/server/am/SameProcessApplicationThread.java
+++ b/services/core/java/com/android/server/am/SameProcessApplicationThread.java
@@ -48,12 +48,12 @@
     @Override
     public void scheduleReceiver(Intent intent, ActivityInfo info, CompatibilityInfo compatInfo,
             int resultCode, String data, Bundle extras, boolean ordered, boolean assumeDelivered,
-            int sendingUser, int processState, int sentFromUid, String sentFromPackage) {
+            int sendingUser, int processState, int sendingUid, String sendingPackage) {
         mHandler.post(() -> {
             try {
                 mWrapped.scheduleReceiver(intent, info, compatInfo, resultCode, data, extras,
-                        ordered, assumeDelivered, sendingUser, processState, sentFromUid,
-                        sentFromPackage);
+                        ordered, assumeDelivered, sendingUser, processState, sendingUid,
+                        sendingPackage);
             } catch (RemoteException e) {
                 throw new RuntimeException(e);
             }
@@ -63,12 +63,12 @@
     @Override
     public void scheduleRegisteredReceiver(IIntentReceiver receiver, Intent intent, int resultCode,
             String data, Bundle extras, boolean ordered, boolean sticky, boolean assumeDelivered,
-            int sendingUser, int processState, int sentFromUid, String sentFromPackage) {
+            int sendingUser, int processState, int sendingUid, String sendingPackage) {
         mHandler.post(() -> {
             try {
                 mWrapped.scheduleRegisteredReceiver(receiver, intent, resultCode, data, extras,
-                        ordered, sticky, assumeDelivered, sendingUser, processState, sentFromUid,
-                        sentFromPackage);
+                        ordered, sticky, assumeDelivered, sendingUser, processState, sendingUid,
+                        sendingPackage);
             } catch (RemoteException e) {
                 throw new RuntimeException(e);
             }
@@ -82,11 +82,11 @@
             if (r.registered) {
                 scheduleRegisteredReceiver(r.receiver, r.intent,
                         r.resultCode, r.data, r.extras, r.ordered, r.sticky, r.assumeDelivered,
-                        r.sendingUser, r.processState, r.sentFromUid, r.sentFromPackage);
+                        r.sendingUser, r.processState, r.sendingUid, r.sendingPackage);
             } else {
                 scheduleReceiver(r.intent, r.activityInfo, r.compatInfo,
                         r.resultCode, r.data, r.extras, r.sync, r.assumeDelivered,
-                        r.sendingUser, r.processState, r.sentFromUid, r.sentFromPackage);
+                        r.sendingUser, r.processState, r.sendingUid, r.sendingPackage);
             }
         }
     }
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index c5b611d..b0a14ab 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -1986,10 +1986,6 @@
             Slogf.w(TAG, "Cannot switch to User #" + targetUserId + ": not supported");
             return false;
         }
-        if (targetUserInfo.isProfile()) {
-            Slogf.w(TAG, "Cannot switch to User #" + targetUserId + ": not a full user");
-            return false;
-        }
         if (FactoryResetter.isFactoryResetting()) {
             Slogf.w(TAG, "Cannot switch to User #" + targetUserId + ": factory reset in progress");
             return false;
diff --git a/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java
index 704b425..f520f6a 100644
--- a/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java
+++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java
@@ -16,9 +16,12 @@
 
 package com.android.server.appop;
 
+import static android.app.AppOpsManager.MODE_ALLOWED;
 import static android.app.AppOpsManager.OP_NONE;
+import static android.app.AppOpsManager.OP_SCHEDULE_EXACT_ALARM;
 import static android.app.AppOpsManager.WATCH_FOREGROUND_CHANGES;
 import static android.app.AppOpsManager.opRestrictsRead;
+import static android.app.AppOpsManager.opToDefaultMode;
 
 import static com.android.server.appop.AppOpsService.ModeCallback.ALL_OPS;
 
@@ -31,24 +34,46 @@
 import android.app.AppOpsManager.Mode;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
+import android.content.pm.UserPackage;
+import android.os.AsyncTask;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.AtomicFile;
+import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
+import android.util.Xml;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.XmlUtils;
 import com.android.internal.util.function.pooled.PooledLambda;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+import com.android.server.LocalServices;
+import com.android.server.pm.UserManagerInternal;
+import com.android.server.pm.permission.PermissionManagerServiceInternal;
 
 import libcore.util.EmptyArray;
 
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
 import java.io.PrintWriter;
+import java.util.ArrayList;
 import java.util.Collections;
+import java.util.List;
 import java.util.Objects;
 
 
@@ -60,6 +85,36 @@
 
     static final String TAG = "LegacyAppOpsServiceInterfaceImpl";
 
+    private static final boolean DEBUG = false;
+
+    // Write at most every 30 minutes.
+    private static final long WRITE_DELAY = DEBUG ? 1000 : 30 * 60 * 1000;
+
+    /**
+     * Sentinel integer version to denote that there was no appops.xml found on boot.
+     * This will happen when a device boots with no existing userdata.
+     */
+    private static final int NO_FILE_VERSION = -2;
+
+    /**
+     * Sentinel integer version to denote that there was no version in the appops.xml found on boot.
+     * This means the file is coming from a build before versioning was added.
+     */
+    private static final int NO_VERSION = -1;
+
+    /**
+     * Increment by one every time and add the corresponding upgrade logic in
+     * {@link #upgradeLocked(int)} below. The first version was 1.
+     */
+    @VisibleForTesting
+    static final int CURRENT_VERSION = 3;
+
+    /**
+     * This stores the version of appops.xml seen at boot. If this is smaller than
+     * {@link #CURRENT_VERSION}, then we will run {@link #upgradeLocked(int)} on startup.
+     */
+    private int mVersionAtBoot = NO_FILE_VERSION;
+
     // Must be the same object that the AppOpsService is using for locking.
     final Object mLock;
     final Handler mHandler;
@@ -77,17 +132,35 @@
     final ArrayMap<String, ArraySet<OnOpModeChangedListener>> mPackageModeWatchers =
             new ArrayMap<>();
 
-    final PersistenceScheduler mPersistenceScheduler;
+    final AtomicFile mFile;
+    final Runnable mWriteRunner = new Runnable() {
+        public void run() {
+            synchronized (mLock) {
+                mWriteScheduled = false;
+                mFastWriteScheduled = false;
+                AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
+                    @Override
+                    protected Void doInBackground(Void... params) {
+                        writeState();
+                        return null;
+                    }
+                };
+                task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
+            }
+        }
+    };
+
+    boolean mWriteScheduled;
+    boolean mFastWriteScheduled;
 
 
     // Constant meaning that any UID should be matched when dispatching callbacks
     private static final int UID_ANY = -2;
 
-
-    AppOpsCheckingServiceImpl(PersistenceScheduler persistenceScheduler,
+    AppOpsCheckingServiceImpl(File storageFile,
             @NonNull Object lock, Handler handler, Context context,
             SparseArray<int[]> switchedOps) {
-        this.mPersistenceScheduler = persistenceScheduler;
+        this.mFile = new AtomicFile(storageFile);
         this.mLock = lock;
         this.mHandler = handler;
         this.mContext = context;
@@ -95,6 +168,15 @@
     }
 
     @Override
+    public void systemReady() {
+        synchronized (mLock) {
+            // TODO: This file version upgrade code may still need to happen after we switch to
+            //  another implementation of AppOpsCheckingServiceInterface.
+            upgradeLocked(mVersionAtBoot);
+        }
+    }
+
+    @Override
     public SparseIntArray getNonDefaultUidModes(int uid) {
         synchronized (mLock) {
             SparseIntArray opModes = mUidModes.get(uid, null);
@@ -141,7 +223,7 @@
                     opModes = new SparseIntArray();
                     mUidModes.put(uid, opModes);
                     opModes.put(op, mode);
-                    mPersistenceScheduler.scheduleWriteLocked();
+                    scheduleWriteLocked();
                 }
             } else {
                 if (opModes.indexOfKey(op) >= 0 && opModes.get(op) == mode) {
@@ -156,7 +238,7 @@
                 } else {
                     opModes.put(op, mode);
                 }
-                mPersistenceScheduler.scheduleWriteLocked();
+                scheduleWriteLocked();
             }
         }
         return true;
@@ -192,7 +274,7 @@
                     opModes = new SparseIntArray();
                     packageModes.put(packageName, opModes);
                     opModes.put(op, mode);
-                    mPersistenceScheduler.scheduleWriteLocked();
+                    scheduleWriteLocked();
                 }
             } else {
                 if (opModes.indexOfKey(op) >= 0 && opModes.get(op) == mode) {
@@ -207,7 +289,7 @@
                 } else {
                     opModes.put(op, mode);
                 }
-                mPersistenceScheduler.scheduleWriteLocked();
+                scheduleWriteLocked();
             }
         }
     }
@@ -220,7 +302,7 @@
                 return;
             }
             mUidModes.remove(uid);
-            mPersistenceScheduler.scheduleFastWriteLocked();
+            scheduleFastWriteLocked();
         }
     }
 
@@ -253,7 +335,7 @@
             }
             SparseIntArray ops = packageModes.remove(packageName);
             if (ops != null) {
-                mPersistenceScheduler.scheduleFastWriteLocked();
+                scheduleFastWriteLocked();
                 return true;
             }
             return false;
@@ -613,4 +695,487 @@
         return needSep;
     }
 
+    private void scheduleWriteLocked() {
+        if (!mWriteScheduled) {
+            mWriteScheduled = true;
+            mHandler.postDelayed(mWriteRunner, WRITE_DELAY);
+        }
+    }
+
+    private void scheduleFastWriteLocked() {
+        if (!mFastWriteScheduled) {
+            mWriteScheduled = true;
+            mFastWriteScheduled = true;
+            mHandler.removeCallbacks(mWriteRunner);
+            mHandler.postDelayed(mWriteRunner, 10 * 1000);
+        }
+    }
+
+    @Override
+    public void writeState() {
+        synchronized (mFile) {
+            FileOutputStream stream;
+            try {
+                stream = mFile.startWrite();
+            } catch (IOException e) {
+                Slog.w(TAG, "Failed to write state: " + e);
+                return;
+            }
+
+            try {
+                TypedXmlSerializer out = Xml.resolveSerializer(stream);
+                out.startDocument(null, true);
+                out.startTag(null, "app-ops");
+                out.attributeInt(null, "v", CURRENT_VERSION);
+
+                SparseArray<SparseIntArray> uidModesCopy = new SparseArray<>();
+                SparseArray<ArrayMap<String, SparseIntArray>> userPackageModesCopy =
+                        new SparseArray<>();
+                int uidModesSize;
+                int usersSize;
+                synchronized (mLock) {
+                    uidModesSize = mUidModes.size();
+                    for (int uidIdx = 0; uidIdx < uidModesSize; uidIdx++) {
+                        int uid = mUidModes.keyAt(uidIdx);
+                        SparseIntArray modes = mUidModes.valueAt(uidIdx);
+                        uidModesCopy.put(uid, modes.clone());
+                    }
+                    usersSize = mUserPackageModes.size();
+                    for (int userIdx = 0; userIdx < usersSize; userIdx++) {
+                        int user = mUserPackageModes.keyAt(userIdx);
+                        ArrayMap<String, SparseIntArray> packageModes =
+                                mUserPackageModes.valueAt(userIdx);
+                        ArrayMap<String, SparseIntArray> packageModesCopy = new ArrayMap<>();
+                        userPackageModesCopy.put(user, packageModesCopy);
+                        for (int pkgIdx = 0, packageModesSize = packageModes.size();
+                                pkgIdx < packageModesSize; pkgIdx++) {
+                            String pkg = packageModes.keyAt(pkgIdx);
+                            SparseIntArray modes = packageModes.valueAt(pkgIdx);
+                            packageModesCopy.put(pkg, modes.clone());
+                        }
+                    }
+                }
+
+                for (int uidStateNum = 0; uidStateNum < uidModesSize; uidStateNum++) {
+                    int uid = uidModesCopy.keyAt(uidStateNum);
+                    SparseIntArray modes = uidModesCopy.valueAt(uidStateNum);
+
+                    out.startTag(null, "uid");
+                    out.attributeInt(null, "n", uid);
+
+                    final int modesSize = modes.size();
+                    for (int modeIdx = 0; modeIdx < modesSize; modeIdx++) {
+                        final int op = modes.keyAt(modeIdx);
+                        final int mode = modes.valueAt(modeIdx);
+                        out.startTag(null, "op");
+                        out.attributeInt(null, "n", op);
+                        out.attributeInt(null, "m", mode);
+                        out.endTag(null, "op");
+                    }
+                    out.endTag(null, "uid");
+                }
+
+                for (int userIdx = 0; userIdx < usersSize; userIdx++) {
+                    int userId = userPackageModesCopy.keyAt(userIdx);
+                    ArrayMap<String, SparseIntArray> packageModes =
+                            userPackageModesCopy.valueAt(userIdx);
+
+                    out.startTag(null, "user");
+                    out.attributeInt(null, "n", userId);
+
+                    int packageModesSize = packageModes.size();
+                    for (int pkgIdx = 0; pkgIdx < packageModesSize; pkgIdx++) {
+                        String pkg = packageModes.keyAt(pkgIdx);
+                        SparseIntArray modes = packageModes.valueAt(pkgIdx);
+
+                        out.startTag(null, "pkg");
+                        out.attribute(null, "n", pkg);
+
+                        final int modesSize = modes.size();
+                        for (int modeIdx = 0; modeIdx < modesSize; modeIdx++) {
+                            final int op = modes.keyAt(modeIdx);
+                            final int mode = modes.valueAt(modeIdx);
+
+                            out.startTag(null, "op");
+                            out.attributeInt(null, "n", op);
+                            out.attributeInt(null, "m", mode);
+                            out.endTag(null, "op");
+                        }
+                        out.endTag(null, "pkg");
+                    }
+                    out.endTag(null, "user");
+                }
+
+                out.endTag(null, "app-ops");
+                out.endDocument();
+                mFile.finishWrite(stream);
+            } catch (IOException e) {
+                Slog.w(TAG, "Failed to write state, restoring backup.", e);
+                mFile.failWrite(stream);
+            }
+        }
+    }
+
+    /* Current format
+        <uid>
+          <op>
+        </uid>
+
+        <user>
+          <pkg>
+            <op>
+          </pkg>
+        </user>
+     */
+
+    @Override
+    public void readState() {
+        synchronized (mFile) {
+            synchronized (mLock) {
+                FileInputStream stream;
+                try {
+                    stream = mFile.openRead();
+                } catch (FileNotFoundException e) {
+                    Slog.i(TAG, "No existing app ops " + mFile.getBaseFile() + "; starting empty");
+                    mVersionAtBoot = NO_FILE_VERSION;
+                    return;
+                }
+
+                try {
+                    TypedXmlPullParser parser = Xml.resolvePullParser(stream);
+                    int type;
+                    while ((type = parser.next()) != XmlPullParser.START_TAG
+                            && type != XmlPullParser.END_DOCUMENT) {
+                        // Parse next until we reach the start or end
+                    }
+
+                    if (type != XmlPullParser.START_TAG) {
+                        throw new IllegalStateException("no start tag found");
+                    }
+
+                    mVersionAtBoot = parser.getAttributeInt(null, "v", NO_VERSION);
+
+                    int outerDepth = parser.getDepth();
+                    while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                            && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+                        if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                            continue;
+                        }
+
+                        String tagName = parser.getName();
+                        if (tagName.equals("pkg")) {
+                            // version 2 has the structure pkg -> uid -> op ->
+                            // in version 3, since pkg and uid states are kept completely
+                            // independent we switch to user -> pkg -> op
+                            readPackage(parser);
+                        } else if (tagName.equals("uid")) {
+                            readUidOps(parser);
+                        } else if (tagName.equals("user")) {
+                            readUser(parser);
+                        } else {
+                            Slog.w(TAG, "Unknown element under <app-ops>: "
+                                    + parser.getName());
+                            XmlUtils.skipCurrentTag(parser);
+                        }
+                    }
+                    return;
+                } catch (XmlPullParserException e) {
+                    throw new RuntimeException(e);
+                } catch (IOException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void shutdown() {
+        boolean doWrite = false;
+        synchronized (this) {
+            if (mWriteScheduled) {
+                mWriteScheduled = false;
+                mFastWriteScheduled = false;
+                mHandler.removeCallbacks(mWriteRunner);
+                doWrite = true;
+            }
+        }
+        if (doWrite) {
+            writeState();
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void readUidOps(TypedXmlPullParser parser) throws NumberFormatException,
+            XmlPullParserException, IOException {
+        final int uid = parser.getAttributeInt(null, "n");
+        SparseIntArray modes = mUidModes.get(uid);
+        if (modes == null) {
+            modes = new SparseIntArray();
+            mUidModes.put(uid, modes);
+        }
+
+        int outerDepth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+
+            String tagName = parser.getName();
+            if (tagName.equals("op")) {
+                final int code = parser.getAttributeInt(null, "n");
+                final int mode = parser.getAttributeInt(null, "m");
+
+                if (mode != opToDefaultMode(code)) {
+                    modes.put(code, mode);
+                }
+            } else {
+                Slog.w(TAG, "Unknown element under <uid>: "
+                        + parser.getName());
+                XmlUtils.skipCurrentTag(parser);
+            }
+        }
+    }
+
+    /*
+     * Used for migration when pkg is the depth=1 tag
+     */
+    @GuardedBy("mLock")
+    private void readPackage(TypedXmlPullParser parser)
+            throws NumberFormatException, XmlPullParserException, IOException {
+        String pkgName = parser.getAttributeValue(null, "n");
+        int outerDepth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+
+            String tagName = parser.getName();
+            if (tagName.equals("uid")) {
+                readUid(parser, pkgName);
+            } else {
+                Slog.w(TAG, "Unknown element under <pkg>: "
+                        + parser.getName());
+                XmlUtils.skipCurrentTag(parser);
+            }
+        }
+    }
+
+    /*
+     * Used for migration when uid is the depth=2 tag
+     */
+    @GuardedBy("mLock")
+    private void readUid(TypedXmlPullParser parser, String pkgName)
+            throws NumberFormatException, XmlPullParserException, IOException {
+        int userId = UserHandle.getUserId(parser.getAttributeInt(null, "n"));
+        int outerDepth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+
+            String tagName = parser.getName();
+            if (tagName.equals("op")) {
+                readOp(parser, userId, pkgName);
+            } else {
+                Slog.w(TAG, "Unknown element under <pkg>: "
+                        + parser.getName());
+                XmlUtils.skipCurrentTag(parser);
+            }
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void readUser(TypedXmlPullParser parser)
+            throws NumberFormatException, XmlPullParserException, IOException {
+        int userId = parser.getAttributeInt(null, "n");
+        int outerDepth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+
+            String tagName = parser.getName();
+            if (tagName.equals("pkg")) {
+                readPackage(parser, userId);
+            } else {
+                Slog.w(TAG, "Unknown element under <user>: "
+                        + parser.getName());
+                XmlUtils.skipCurrentTag(parser);
+            }
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void readPackage(TypedXmlPullParser parser, int userId)
+            throws NumberFormatException, XmlPullParserException, IOException {
+        String pkgName = parser.getAttributeValue(null, "n");
+        int outerDepth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+
+            String tagName = parser.getName();
+            if (tagName.equals("op")) {
+                readOp(parser, userId, pkgName);
+            } else {
+                Slog.w(TAG, "Unknown element under <pkg>: "
+                        + parser.getName());
+                XmlUtils.skipCurrentTag(parser);
+            }
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void readOp(TypedXmlPullParser parser, int userId, @NonNull String pkgName)
+            throws NumberFormatException, XmlPullParserException {
+        final int opCode = parser.getAttributeInt(null, "n");
+        final int defaultMode = AppOpsManager.opToDefaultMode(opCode);
+        final int mode = parser.getAttributeInt(null, "m", defaultMode);
+
+        if (mode != defaultMode) {
+            ArrayMap<String, SparseIntArray> packageModes = mUserPackageModes.get(userId);
+            if (packageModes == null) {
+                packageModes = new ArrayMap<>();
+                mUserPackageModes.put(userId, packageModes);
+            }
+
+            SparseIntArray modes = packageModes.get(pkgName);
+            if (modes == null) {
+                modes = new SparseIntArray();
+                packageModes.put(pkgName, modes);
+            }
+
+            modes.put(opCode, mode);
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void upgradeLocked(int oldVersion) {
+        if (oldVersion == NO_FILE_VERSION || oldVersion >= CURRENT_VERSION) {
+            return;
+        }
+        Slog.d(TAG, "Upgrading app-ops xml from version " + oldVersion + " to " + CURRENT_VERSION);
+        switch (oldVersion) {
+            case NO_VERSION:
+                upgradeRunAnyInBackgroundLocked();
+                // fall through
+            case 1:
+                upgradeScheduleExactAlarmLocked();
+                // fall through
+            case 2:
+                // for future upgrades
+        }
+        scheduleFastWriteLocked();
+    }
+
+    /**
+     * For all installed apps at time of upgrade, OP_RUN_ANY_IN_BACKGROUND will inherit the mode
+     *  from RUN_IN_BACKGROUND.
+     */
+    @VisibleForTesting
+    @GuardedBy("mLock")
+    void upgradeRunAnyInBackgroundLocked() {
+        final int uidModesSize = mUidModes.size();
+        for (int uidIdx = 0; uidIdx < uidModesSize; uidIdx++) {
+            SparseIntArray modesForUid = mUidModes.valueAt(uidIdx);
+
+            final int idx = modesForUid.indexOfKey(AppOpsManager.OP_RUN_IN_BACKGROUND);
+            if (idx >= 0) {
+                // Only non-default should exist in the map
+                modesForUid.put(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, modesForUid.valueAt(idx));
+            }
+        }
+
+        final int usersSize = mUserPackageModes.size();
+        for (int userIdx = 0; userIdx < usersSize; userIdx++) {
+            ArrayMap<String, SparseIntArray> packageModes =
+                    mUserPackageModes.valueAt(userIdx);
+
+            for (int pkgIdx = 0, packageModesSize = packageModes.size();
+                    pkgIdx < packageModesSize; pkgIdx++) {
+                SparseIntArray modes = packageModes.valueAt(pkgIdx);
+
+                final int idx = modes.indexOfKey(AppOpsManager.OP_RUN_IN_BACKGROUND);
+                if (idx >= 0) {
+                    // Only non-default should exist in the map
+                    modes.put(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, modes.valueAt(idx));
+                }
+            }
+        }
+    }
+
+    /**
+     * The interpretation of the default mode - MODE_DEFAULT - for OP_SCHEDULE_EXACT_ALARM is
+     * changing. Simultaneously, we want to change this op's mode from MODE_DEFAULT to MODE_ALLOWED
+     * for already installed apps. For newer apps, it will stay as MODE_DEFAULT.
+     */
+    @VisibleForTesting
+    @GuardedBy("mLock")
+    void upgradeScheduleExactAlarmLocked() {
+        final PermissionManagerServiceInternal pmsi = LocalServices.getService(
+                PermissionManagerServiceInternal.class);
+        final UserManagerInternal umi = LocalServices.getService(UserManagerInternal.class);
+        final PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
+
+        final String[] packagesDeclaringPermission = pmsi.getAppOpPermissionPackages(
+                AppOpsManager.opToPermission(OP_SCHEDULE_EXACT_ALARM));
+        final int[] userIds = umi.getUserIds();
+
+        for (final String pkg : packagesDeclaringPermission) {
+            for (int userId : userIds) {
+                final int uid = pmi.getPackageUid(pkg, 0, userId);
+                final int oldMode = getUidMode(uid, OP_SCHEDULE_EXACT_ALARM);
+                if (oldMode == AppOpsManager.opToDefaultMode(OP_SCHEDULE_EXACT_ALARM)) {
+                    setUidMode(uid, OP_SCHEDULE_EXACT_ALARM, MODE_ALLOWED);
+                }
+            }
+            // This appop is meant to be controlled at a uid level. So we leave package modes as
+            // they are.
+        }
+    }
+
+    @VisibleForTesting
+    List<Integer> getUidsWithNonDefaultModes() {
+        List<Integer> result = new ArrayList<>();
+        synchronized (mLock) {
+            for (int i = 0; i < mUidModes.size(); i++) {
+                SparseIntArray modes = mUidModes.valueAt(i);
+                if (modes.size() > 0) {
+                    result.add(mUidModes.keyAt(i));
+                }
+            }
+        }
+
+        return result;
+    }
+
+    @VisibleForTesting
+    List<UserPackage> getPackagesWithNonDefaultModes() {
+        List<UserPackage> result = new ArrayList<>();
+        synchronized (mLock) {
+            for (int i = 0; i < mUserPackageModes.size(); i++) {
+                ArrayMap<String, SparseIntArray> packageModes = mUserPackageModes.valueAt(i);
+                for (int j = 0; j < packageModes.size(); j++) {
+                    SparseIntArray modes = packageModes.valueAt(j);
+                    if (modes.size() > 0) {
+                        result.add(
+                                UserPackage.of(mUserPackageModes.keyAt(i), packageModes.keyAt(j)));
+                    }
+                }
+            }
+        }
+
+        return result;
+    }
 }
\ No newline at end of file
diff --git a/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java
index 9a564fc..9096898 100644
--- a/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java
+++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java
@@ -23,6 +23,8 @@
 import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.io.PrintWriter;
 
 /**
@@ -31,6 +33,32 @@
  * In the future this interface will also include op restrictions.
  */
 public interface AppOpsCheckingServiceInterface {
+
+    /**
+     * Tells the checking service to write its state to persistence (unconditionally).
+     * This is only made visible for testing.
+     */
+    @VisibleForTesting
+    void writeState();
+
+    /**
+     * Tells the checking service to read its state from persistence. This is generally called
+     * shortly after instantiation. If extra system services need to be guaranteed to be published
+     * that work should be done in {@link #systemReady()}
+     */
+    void readState();
+
+    /**
+     * Tells the checking service that a shutdown is occurring. This gives it a chance to write its
+     * state to persistence (if there are any pending changes).
+     */
+    void shutdown();
+
+    /**
+     * Do additional initialization work that is dependent on external system services.
+     */
+    void systemReady();
+
     /**
      * Returns a copy of non-default app-ops with op as keys and their modes as values for a uid.
      * Returns an empty SparseIntArray if nothing is set.
diff --git a/services/core/java/com/android/server/appop/AppOpsCheckingServiceLoggingDecorator.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceLoggingDecorator.java
index b8326ad..0094b86 100644
--- a/services/core/java/com/android/server/appop/AppOpsCheckingServiceLoggingDecorator.java
+++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceLoggingDecorator.java
@@ -40,6 +40,30 @@
     }
 
     @Override
+    public void writeState() {
+        Log.i(LOG_TAG, "writeState()");
+        mService.writeState();
+    }
+
+    @Override
+    public void readState() {
+        Log.i(LOG_TAG, "readState()");
+        mService.readState();
+    }
+
+    @Override
+    public void shutdown() {
+        Log.i(LOG_TAG, "shutdown()");
+        mService.shutdown();
+    }
+
+    @Override
+    public void systemReady() {
+        Log.i(LOG_TAG, "systemReady()");
+        mService.systemReady();
+    }
+
+    @Override
     public SparseIntArray getNonDefaultUidModes(int uid) {
         Log.i(LOG_TAG, "getNonDefaultUidModes(uid = " + uid + ")");
         return mService.getNonDefaultUidModes(uid);
diff --git a/services/core/java/com/android/server/appop/AppOpsCheckingServiceTracingDecorator.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceTracingDecorator.java
new file mode 100644
index 0000000..dd06464
--- /dev/null
+++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceTracingDecorator.java
@@ -0,0 +1,334 @@
+/*
+ * 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.server.appop;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.AppOpsManager;
+import android.os.Trace;
+import android.util.ArraySet;
+import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
+
+import java.io.PrintWriter;
+
+/**
+ * Surrounds all AppOpsCheckingServiceInterface method calls with Trace.traceBegin and
+ * Trace.traceEnd. These traces are used for performance testing.
+ */
+public class AppOpsCheckingServiceTracingDecorator implements AppOpsCheckingServiceInterface {
+    private static final long TRACE_TAG = Trace.TRACE_TAG_SYSTEM_SERVER;
+    private final AppOpsCheckingServiceInterface mService;
+
+    AppOpsCheckingServiceTracingDecorator(
+            @NonNull AppOpsCheckingServiceInterface appOpsCheckingServiceInterface) {
+        mService = appOpsCheckingServiceInterface;
+    }
+
+    @Override
+    public void writeState() {
+        Trace.traceBegin(TRACE_TAG,
+                "TaggedTracingAppOpsCheckingServiceInterfaceImpl#writeState");
+        try {
+            mService.writeState();
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @Override
+    public void readState() {
+        Trace.traceBegin(TRACE_TAG,
+                "TaggedTracingAppOpsCheckingServiceInterfaceImpl#readState");
+        try {
+            mService.readState();
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @Override
+    public void shutdown() {
+        Trace.traceBegin(TRACE_TAG,
+                "TaggedTracingAppOpsCheckingServiceInterfaceImpl#shutdown");
+        try {
+            mService.shutdown();
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @Override
+    public void systemReady() {
+        Trace.traceBegin(TRACE_TAG,
+                "TaggedTracingAppOpsCheckingServiceInterfaceImpl#systemReady");
+        try {
+            mService.systemReady();
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @Override
+    public SparseIntArray getNonDefaultUidModes(int uid) {
+        Trace.traceBegin(TRACE_TAG,
+                "TaggedTracingAppOpsCheckingServiceInterfaceImpl#getNonDefaultUidModes");
+        try {
+            return mService.getNonDefaultUidModes(uid);
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @Override
+    public SparseIntArray getNonDefaultPackageModes(String packageName, int userId) {
+        Trace.traceBegin(TRACE_TAG,
+                "TaggedTracingAppOpsCheckingServiceInterfaceImpl#getNonDefaultPackageModes");
+        try {
+            return mService.getNonDefaultPackageModes(packageName, userId);
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @Override
+    public int getUidMode(int uid, int op) {
+        Trace.traceBegin(TRACE_TAG, "TaggedTracingAppOpsCheckingServiceInterfaceImpl#getUidMode");
+        try {
+            return mService.getUidMode(uid, op);
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @Override
+    public boolean setUidMode(int uid, int op, @AppOpsManager.Mode int mode) {
+        Trace.traceBegin(TRACE_TAG, "TaggedTracingAppOpsCheckingServiceInterfaceImpl#setUidMode");
+        try {
+            return mService.setUidMode(uid, op, mode);
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @Override
+    public int getPackageMode(@NonNull String packageName, int op, @UserIdInt int userId) {
+        Trace.traceBegin(TRACE_TAG,
+                "TaggedTracingAppOpsCheckingServiceInterfaceImpl#getPackageMode");
+        try {
+            return mService.getPackageMode(packageName, op, userId);
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @Override
+    public void setPackageMode(@NonNull String packageName, int op, @AppOpsManager.Mode int mode,
+            @UserIdInt int userId) {
+        Trace.traceBegin(TRACE_TAG,
+                "TaggedTracingAppOpsCheckingServiceInterfaceImpl#setPackageMode");
+        try {
+            mService.setPackageMode(packageName, op, mode, userId);
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @Override
+    public boolean removePackage(@NonNull String packageName, @UserIdInt int userId) {
+        Trace.traceBegin(TRACE_TAG,
+                "TaggedTracingAppOpsCheckingServiceInterfaceImpl#removePackage");
+        try {
+            return mService.removePackage(packageName, userId);
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @Override
+    public void removeUid(int uid) {
+        Trace.traceBegin(TRACE_TAG,
+                "TaggedTracingAppOpsCheckingServiceInterfaceImpl#removeUid");
+        try {
+            mService.removeUid(uid);
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @Override
+    public boolean areUidModesDefault(int uid) {
+        Trace.traceBegin(TRACE_TAG,
+                "TaggedTracingAppOpsCheckingServiceInterfaceImpl#areUidModesDefault");
+        try {
+            return mService.areUidModesDefault(uid);
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @Override
+    public boolean arePackageModesDefault(String packageName, @UserIdInt int userId) {
+        Trace.traceBegin(TRACE_TAG,
+                "TaggedTracingAppOpsCheckingServiceInterfaceImpl#arePackageModesDefault");
+        try {
+            return mService.arePackageModesDefault(packageName, userId);
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @Override
+    public void clearAllModes() {
+        Trace.traceBegin(TRACE_TAG,
+                "TaggedTracingAppOpsCheckingServiceInterfaceImpl#clearAllModes");
+        try {
+            mService.clearAllModes();
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @Override
+    public void startWatchingOpModeChanged(@NonNull OnOpModeChangedListener changedListener,
+            int op) {
+        Trace.traceBegin(TRACE_TAG,
+                "TaggedTracingAppOpsCheckingServiceInterfaceImpl#startWatchingOpModeChanged");
+        try {
+            mService.startWatchingOpModeChanged(changedListener, op);
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @Override
+    public void startWatchingPackageModeChanged(@NonNull OnOpModeChangedListener changedListener,
+            @NonNull String packageName) {
+        Trace.traceBegin(TRACE_TAG,
+                "TaggedTracingAppOpsCheckingServiceInterfaceImpl#startWatchingPackageModeChanged");
+        try {
+            mService.startWatchingPackageModeChanged(changedListener, packageName);
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @Override
+    public void removeListener(@NonNull OnOpModeChangedListener changedListener) {
+        Trace.traceBegin(TRACE_TAG,
+                "TaggedTracingAppOpsCheckingServiceInterfaceImpl#removeListener");
+        try {
+            mService.removeListener(changedListener);
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @Override
+    public ArraySet<OnOpModeChangedListener> getOpModeChangedListeners(int op) {
+        Trace.traceBegin(TRACE_TAG,
+                "TaggedTracingAppOpsCheckingServiceInterfaceImpl#getOpModeChangedListeners");
+        try {
+            return mService.getOpModeChangedListeners(op);
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @Override
+    public ArraySet<OnOpModeChangedListener> getPackageModeChangedListeners(
+            @NonNull String packageName) {
+        Trace.traceBegin(TRACE_TAG,
+                "TaggedTracingAppOpsCheckingServiceInterfaceImpl#getPackageModeChangedListeners");
+        try {
+            return mService.getPackageModeChangedListeners(packageName);
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @Override
+    public void notifyWatchersOfChange(int op, int uid) {
+        Trace.traceBegin(TRACE_TAG,
+                "TaggedTracingAppOpsCheckingServiceInterfaceImpl#notifyWatchersOfChange");
+        try {
+            mService.notifyWatchersOfChange(op, uid);
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @Override
+    public void notifyOpChanged(@NonNull OnOpModeChangedListener changedListener, int op, int uid,
+            @Nullable String packageName) {
+        Trace.traceBegin(TRACE_TAG,
+                "TaggedTracingAppOpsCheckingServiceInterfaceImpl#notifyOpChanged");
+        try {
+            mService.notifyOpChanged(changedListener, op, uid, packageName);
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @Override
+    public void notifyOpChangedForAllPkgsInUid(int op, int uid, boolean onlyForeground,
+            @Nullable OnOpModeChangedListener callbackToIgnore) {
+        Trace.traceBegin(TRACE_TAG,
+                "TaggedTracingAppOpsCheckingServiceInterfaceImpl#notifyOpChangedForAllPkgsInUid");
+        try {
+            mService.notifyOpChangedForAllPkgsInUid(op, uid, onlyForeground, callbackToIgnore);
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @Override
+    public SparseBooleanArray evalForegroundUidOps(int uid, SparseBooleanArray foregroundOps) {
+        Trace.traceBegin(TRACE_TAG,
+                "TaggedTracingAppOpsCheckingServiceInterfaceImpl#evalForegroundUidOps");
+        try {
+            return mService.evalForegroundUidOps(uid, foregroundOps);
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @Override
+    public SparseBooleanArray evalForegroundPackageOps(String packageName,
+            SparseBooleanArray foregroundOps, @UserIdInt int userId) {
+        Trace.traceBegin(TRACE_TAG,
+                "TaggedTracingAppOpsCheckingServiceInterfaceImpl#evalForegroundPackageOps");
+        try {
+            return mService.evalForegroundPackageOps(packageName, foregroundOps, userId);
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+
+    @Override
+    public boolean dumpListeners(int dumpOp, int dumpUid, String dumpPackage,
+            PrintWriter printWriter) {
+        Trace.traceBegin(TRACE_TAG,
+                "TaggedTracingAppOpsCheckingServiceInterfaceImpl#dumpListeners");
+        try {
+            return mService.dumpListeners(dumpOp, dumpUid, dumpPackage, printWriter);
+        } finally {
+            Trace.traceEnd(TRACE_TAG);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 77f16f9..f380978 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -42,7 +42,6 @@
 import static android.app.AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO;
 import static android.app.AppOpsManager.OP_RECORD_AUDIO;
 import static android.app.AppOpsManager.OP_RECORD_AUDIO_HOTWORD;
-import static android.app.AppOpsManager.OP_SCHEDULE_EXACT_ALARM;
 import static android.app.AppOpsManager.OP_VIBRATE;
 import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_FAILED;
 import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_STARTED;
@@ -161,7 +160,6 @@
 import com.android.server.SystemServiceManager;
 import com.android.server.pm.PackageList;
 import com.android.server.pm.UserManagerInternal;
-import com.android.server.pm.permission.PermissionManagerServiceInternal;
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.pm.pkg.PackageState;
 import com.android.server.pm.pkg.PackageStateInternal;
@@ -202,7 +200,7 @@
 import java.util.concurrent.ThreadLocalRandom;
 import java.util.function.Consumer;
 
-public class AppOpsService extends IAppOpsService.Stub implements PersistenceScheduler {
+public class AppOpsService extends IAppOpsService.Stub {
     static final String TAG = "AppOps";
     static final boolean DEBUG = false;
 
@@ -212,26 +210,10 @@
     private final ArraySet<NoteOpTrace> mNoteOpCallerStacktraces = new ArraySet<>();
 
     /**
-     * Sentinel integer version to denote that there was no appops.xml found on boot.
-     * This will happen when a device boots with no existing userdata.
+     * Version of the mRecentAccessesFile.
+     * Increment by one every time an upgrade step is added at boot, none currently exists.
      */
-    private static final int NO_FILE_VERSION = -2;
-
-    /**
-     * Sentinel integer version to denote that there was no version in the appops.xml found on boot.
-     * This means the file is coming from a build before versioning was added.
-     */
-    private static final int NO_VERSION = -1;
-
-    /** Increment by one every time and add the corresponding upgrade logic in
-     *  {@link #upgradeLocked(int)} below. The first version was 1 */
-    static final int CURRENT_VERSION = 2;
-
-    /**
-     * This stores the version of appops.xml seen at boot. If this is smaller than
-     * {@link #CURRENT_VERSION}, then we will run {@link #upgradeLocked(int)} on startup.
-     */
-    private int mVersionAtBoot = NO_FILE_VERSION;
+    private static final int CURRENT_VERSION = 1;
 
     // Write at most every 30 minutes.
     static final long WRITE_DELAY = DEBUG ? 1000 : 30*60*1000;
@@ -251,7 +233,8 @@
     private static final int RARELY_USED_PACKAGES_INITIALIZATION_DELAY_MILLIS = 300000;
 
     final Context mContext;
-    final AtomicFile mFile;
+    final AtomicFile mStorageFile;
+    final AtomicFile mRecentAccessesFile;
     private final @Nullable File mNoteOpCallerStacktracesFile;
     final Handler mHandler;
 
@@ -314,7 +297,7 @@
                 mFastWriteScheduled = false;
                 AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
                     @Override protected Void doInBackground(Void... params) {
-                        writeState();
+                        writeRecentAccesses();
                         return null;
                     }
                 };
@@ -936,7 +919,9 @@
         }
     }
 
-    public AppOpsService(File storagePath, Handler handler, Context context) {
+    @VisibleForTesting
+    public AppOpsService(File recentAccessesFile, File storageFile, Handler handler,
+            Context context) {
         mContext = context;
 
         for (int switchedCode = 0; switchedCode < _NUM_OP; switchedCode++) {
@@ -944,15 +929,17 @@
             mSwitchedOps.put(switchCode,
                     ArrayUtils.appendInt(mSwitchedOps.get(switchCode), switchedCode));
         }
-        mAppOpsCheckingService =
-                new AppOpsCheckingServiceImpl(this, this, handler, context, mSwitchedOps);
+        mAppOpsCheckingService = new AppOpsCheckingServiceTracingDecorator(
+                new AppOpsCheckingServiceImpl(
+                        storageFile, this, handler, context,  mSwitchedOps));
         //mAppOpsCheckingService = new AppOpsCheckingServiceLoggingDecorator(
         //        LocalServices.getService(AppOpsCheckingServiceInterface.class));
-        mAppOpsRestrictions = new AppOpsRestrictionsImpl(context, handler,
-                mAppOpsCheckingService);
+        mAppOpsRestrictions = new AppOpsRestrictionsImpl(context, handler, mAppOpsCheckingService);
 
         LockGuard.installLock(this, LockGuard.INDEX_APP_OPS);
-        mFile = new AtomicFile(storagePath, "appops");
+        mStorageFile = new AtomicFile(storageFile, "appops_legacy");
+        mRecentAccessesFile = new AtomicFile(recentAccessesFile, "appops_accesses");
+
         if (AppOpsManager.NOTE_OP_COLLECTION_ENABLED) {
             mNoteOpCallerStacktracesFile = new File(SystemServiceManager.ensureSystemDir(),
                     "noteOpStackTraces.json");
@@ -962,7 +949,9 @@
         }
         mHandler = handler;
         mConstants = new Constants(mHandler);
-        readState();
+        // To migrate storageFile to recentAccessesFile, these reads must be called in this order.
+        readRecentAccesses();
+        mAppOpsCheckingService.readState();
     }
 
     public void publish() {
@@ -1063,9 +1052,7 @@
     };
 
     public void systemReady() {
-        synchronized (this) {
-            upgradeLocked(mVersionAtBoot);
-        }
+        mAppOpsCheckingService.systemReady();
         initializeUidStates();
 
         getUserManagerInternal().addUserLifecycleListener(
@@ -1243,6 +1230,7 @@
                 int code = packageModes.get(k);
                 ops.put(code, new Op(uidState, packageName, code, uid));
             }
+            uidState.evalForegroundOps();
         }
     }
 
@@ -1317,7 +1305,7 @@
         }
     }
 
-    // The callback method from ForegroundPolicyInterface
+    // The callback method from AppOpsUidStateTracker
     private void onUidStateChanged(int uid, int state, boolean foregroundModeMayChange) {
         synchronized (this) {
             UidState uidState = getUidStateLocked(uid, true);
@@ -1414,12 +1402,12 @@
             }
         }
         if (doWrite) {
-            writeState();
+            writeRecentAccesses();
         }
+        mAppOpsCheckingService.shutdown();
         if (AppOpsManager.NOTE_OP_COLLECTION_ENABLED && mWriteNoteOpsScheduled) {
             writeNoteOps();
         }
-
         mHistoricalRegistry.shutdown();
     }
 
@@ -1691,8 +1679,13 @@
     public void reloadNonHistoricalState() {
         mContext.enforcePermission(Manifest.permission.MANAGE_APPOPS,
                 Binder.getCallingPid(), Binder.getCallingUid(), "reloadNonHistoricalState");
-        writeState();
-        readState();
+        mAppOpsCheckingService.writeState();
+        mAppOpsCheckingService.readState();
+    }
+
+    @VisibleForTesting
+    void readState() {
+        mAppOpsCheckingService.readState();
     }
 
     @Override
@@ -3616,6 +3609,9 @@
         if (mPackageManagerInternal == null) {
             mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
         }
+        if (mPackageManagerInternal == null) {
+            throw new IllegalStateException("PackageManagerInternal not loaded");
+        }
 
         return mPackageManagerInternal;
     }
@@ -3865,16 +3861,14 @@
         return ops;
     }
 
-    @Override
-    public void scheduleWriteLocked() {
+    private void scheduleWriteLocked() {
         if (!mWriteScheduled) {
             mWriteScheduled = true;
             mHandler.postDelayed(mWriteRunner, WRITE_DELAY);
         }
     }
 
-    @Override
-    public void scheduleFastWriteLocked() {
+    private void scheduleFastWriteLocked() {
         if (!mFastWriteScheduled) {
             mWriteScheduled = true;
             mFastWriteScheduled = true;
@@ -3972,14 +3966,28 @@
         return false;
     }
 
-    void readState() {
-        synchronized (mFile) {
+    /**
+     * Read recent accesses from persistence (mRecentAccessesFile).
+     * If there is no mRecentAccessesFile yet, we'll need migrate from mStorageFile: first read from
+     * mStorageFile, then all subsequent reads/writes will use mRecentAccessesFile.
+     * If neither file exists, there's nothing to migrate.
+     */
+    private void readRecentAccesses() {
+        if (!mRecentAccessesFile.exists()) {
+            readRecentAccesses(mStorageFile);
+        } else {
+            readRecentAccesses(mRecentAccessesFile);
+        }
+    }
+
+    private void readRecentAccesses(AtomicFile file) {
+        synchronized (file) {
             synchronized (this) {
                 FileInputStream stream;
                 try {
-                    stream = mFile.openRead();
+                    stream = file.openRead();
                 } catch (FileNotFoundException e) {
-                    Slog.i(TAG, "No existing app ops " + mFile.getBaseFile() + "; starting empty");
+                    Slog.i(TAG, "No existing app ops " + file.getBaseFile() + "; starting empty");
                     return;
                 }
                 boolean success = false;
@@ -3990,15 +3998,13 @@
                     int type;
                     while ((type = parser.next()) != XmlPullParser.START_TAG
                             && type != XmlPullParser.END_DOCUMENT) {
-                        ;
+                        // Parse next until we reach the start or end
                     }
 
                     if (type != XmlPullParser.START_TAG) {
                         throw new IllegalStateException("no start tag found");
                     }
 
-                    mVersionAtBoot = parser.getAttributeInt(null, "v", NO_VERSION);
-
                     int outerDepth = parser.getDepth();
                     while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                             && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
@@ -4010,13 +4016,15 @@
                         if (tagName.equals("pkg")) {
                             readPackage(parser);
                         } else if (tagName.equals("uid")) {
-                            readUidOps(parser);
+                            // uid tag may be present during migration, don't print warning.
+                            XmlUtils.skipCurrentTag(parser);
                         } else {
                             Slog.w(TAG, "Unknown element under <app-ops>: "
                                     + parser.getName());
                             XmlUtils.skipCurrentTag(parser);
                         }
                     }
+
                     success = true;
                 } catch (IllegalStateException e) {
                     Slog.w(TAG, "Failed parsing " + e);
@@ -4044,123 +4052,6 @@
         }
     }
 
-    @VisibleForTesting
-    @GuardedBy("this")
-    void upgradeRunAnyInBackgroundLocked() {
-        for (int i = 0; i < mUidStates.size(); i++) {
-            final UidState uidState = mUidStates.valueAt(i);
-            if (uidState == null) {
-                continue;
-            }
-            SparseIntArray opModes = uidState.getNonDefaultUidModes();
-            if (opModes != null) {
-                final int idx = opModes.indexOfKey(AppOpsManager.OP_RUN_IN_BACKGROUND);
-                if (idx >= 0) {
-                    uidState.setUidMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND,
-                            opModes.valueAt(idx));
-                }
-            }
-            if (uidState.pkgOps == null) {
-                continue;
-            }
-            boolean changed = false;
-            for (int j = 0; j < uidState.pkgOps.size(); j++) {
-                Ops ops = uidState.pkgOps.valueAt(j);
-                if (ops != null) {
-                    final Op op = ops.get(AppOpsManager.OP_RUN_IN_BACKGROUND);
-                    if (op != null && op.getMode() != AppOpsManager.opToDefaultMode(op.op)) {
-                        final Op copy = new Op(op.uidState, op.packageName,
-                                AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, uidState.uid);
-                        copy.setMode(op.getMode());
-                        ops.put(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, copy);
-                        changed = true;
-                    }
-                }
-            }
-            if (changed) {
-                uidState.evalForegroundOps();
-            }
-        }
-    }
-
-    /**
-     * The interpretation of the default mode - MODE_DEFAULT - for OP_SCHEDULE_EXACT_ALARM is
-     * changing. Simultaneously, we want to change this op's mode from MODE_DEFAULT to MODE_ALLOWED
-     * for already installed apps. For newer apps, it will stay as MODE_DEFAULT.
-     */
-    @VisibleForTesting
-    @GuardedBy("this")
-    void upgradeScheduleExactAlarmLocked() {
-        final PermissionManagerServiceInternal pmsi = LocalServices.getService(
-                PermissionManagerServiceInternal.class);
-        final UserManagerInternal umi = LocalServices.getService(UserManagerInternal.class);
-        final PackageManagerInternal pmi = getPackageManagerInternal();
-
-        final String[] packagesDeclaringPermission = pmsi.getAppOpPermissionPackages(
-                AppOpsManager.opToPermission(OP_SCHEDULE_EXACT_ALARM));
-        final int[] userIds = umi.getUserIds();
-
-        for (final String pkg : packagesDeclaringPermission) {
-            for (int userId : userIds) {
-                final int uid = pmi.getPackageUid(pkg, 0, userId);
-
-                UidState uidState = mUidStates.get(uid);
-                if (uidState == null) {
-                    uidState = new UidState(uid);
-                    mUidStates.put(uid, uidState);
-                }
-                final int oldMode = uidState.getUidMode(OP_SCHEDULE_EXACT_ALARM);
-                if (oldMode == AppOpsManager.opToDefaultMode(OP_SCHEDULE_EXACT_ALARM)) {
-                    uidState.setUidMode(OP_SCHEDULE_EXACT_ALARM, MODE_ALLOWED);
-                }
-            }
-            // This appop is meant to be controlled at a uid level. So we leave package modes as
-            // they are.
-        }
-    }
-
-    private void upgradeLocked(int oldVersion) {
-        if (oldVersion >= CURRENT_VERSION) {
-            return;
-        }
-        Slog.d(TAG, "Upgrading app-ops xml from version " + oldVersion + " to " + CURRENT_VERSION);
-        switch (oldVersion) {
-            case NO_VERSION:
-                upgradeRunAnyInBackgroundLocked();
-                // fall through
-            case 1:
-                upgradeScheduleExactAlarmLocked();
-                // fall through
-            case 2:
-                // for future upgrades
-        }
-        scheduleFastWriteLocked();
-    }
-
-    private void readUidOps(TypedXmlPullParser parser) throws NumberFormatException,
-            XmlPullParserException, IOException {
-        final int uid = parser.getAttributeInt(null, "n");
-        int outerDepth = parser.getDepth();
-        int type;
-        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
-                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
-            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
-                continue;
-            }
-
-            String tagName = parser.getName();
-            if (tagName.equals("op")) {
-                final int code = parser.getAttributeInt(null, "n");
-                final int mode = parser.getAttributeInt(null, "m");
-                setUidMode(code, uid, mode);
-            } else {
-                Slog.w(TAG, "Unknown element under <uid-ops>: "
-                        + parser.getName());
-                XmlUtils.skipCurrentTag(parser);
-            }
-        }
-    }
-
     private void readPackage(TypedXmlPullParser parser)
             throws NumberFormatException, XmlPullParserException, IOException {
         String pkgName = parser.getAttributeValue(null, "n");
@@ -4203,7 +4094,6 @@
                 XmlUtils.skipCurrentTag(parser);
             }
         }
-        uidState.evalForegroundOps();
     }
 
     private void readAttributionOp(TypedXmlPullParser parser, @NonNull Op parent,
@@ -4237,9 +4127,6 @@
         int opCode = parser.getAttributeInt(null, "n");
         Op op = new Op(uidState, pkgName, opCode, uidState.uid);
 
-        final int mode = parser.getAttributeInt(null, "m", AppOpsManager.opToDefaultMode(op.op));
-        op.setMode(mode);
-
         int outerDepth = parser.getDepth();
         int type;
         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
@@ -4268,11 +4155,12 @@
         ops.put(op.op, op);
     }
 
-    void writeState() {
-        synchronized (mFile) {
+    @VisibleForTesting
+    void writeRecentAccesses() {
+        synchronized (mRecentAccessesFile) {
             FileOutputStream stream;
             try {
-                stream = mFile.startWrite();
+                stream = mRecentAccessesFile.startWrite();
             } catch (IOException e) {
                 Slog.w(TAG, "Failed to write state: " + e);
                 return;
@@ -4286,41 +4174,6 @@
                 out.startTag(null, "app-ops");
                 out.attributeInt(null, "v", CURRENT_VERSION);
 
-                SparseArray<SparseIntArray> uidStatesClone;
-                synchronized (this) {
-                    uidStatesClone = new SparseArray<>(mUidStates.size());
-
-                    final int uidStateCount = mUidStates.size();
-                    for (int uidStateNum = 0; uidStateNum < uidStateCount; uidStateNum++) {
-                        UidState uidState = mUidStates.valueAt(uidStateNum);
-                        int uid = mUidStates.keyAt(uidStateNum);
-
-                        SparseIntArray opModes = uidState.getNonDefaultUidModes();
-                        if (opModes != null && opModes.size() > 0) {
-                            uidStatesClone.put(uid, opModes);
-                        }
-                    }
-                }
-
-                final int uidStateCount = uidStatesClone.size();
-                for (int uidStateNum = 0; uidStateNum < uidStateCount; uidStateNum++) {
-                    SparseIntArray opModes = uidStatesClone.valueAt(uidStateNum);
-                    if (opModes != null && opModes.size() > 0) {
-                        out.startTag(null, "uid");
-                        out.attributeInt(null, "n", uidStatesClone.keyAt(uidStateNum));
-                        final int opCount = opModes.size();
-                        for (int opCountNum = 0; opCountNum < opCount; opCountNum++) {
-                            final int op = opModes.keyAt(opCountNum);
-                            final int mode = opModes.valueAt(opCountNum);
-                            out.startTag(null, "op");
-                            out.attributeInt(null, "n", op);
-                            out.attributeInt(null, "m", mode);
-                            out.endTag(null, "op");
-                        }
-                        out.endTag(null, "uid");
-                    }
-                }
-
                 if (allOps != null) {
                     String lastPkg = null;
                     for (int i=0; i<allOps.size(); i++) {
@@ -4421,10 +4274,10 @@
 
                 out.endTag(null, "app-ops");
                 out.endDocument();
-                mFile.finishWrite(stream);
+                mRecentAccessesFile.finishWrite(stream);
             } catch (IOException e) {
                 Slog.w(TAG, "Failed to write state, restoring backup.", e);
-                mFile.failWrite(stream);
+                mRecentAccessesFile.failWrite(stream);
             }
         }
         mHistoricalRegistry.writeAndClearDiscreteHistory();
@@ -4871,7 +4724,8 @@
                         synchronized (shell.mInternal) {
                             shell.mInternal.mHandler.removeCallbacks(shell.mInternal.mWriteRunner);
                         }
-                        shell.mInternal.writeState();
+                        shell.mInternal.writeRecentAccesses();
+                        shell.mInternal.mAppOpsCheckingService.writeState();
                         pw.println("Current settings written.");
                     } finally {
                         Binder.restoreCallingIdentity(token);
@@ -4883,7 +4737,8 @@
                             Binder.getCallingUid(), -1);
                     final long token = Binder.clearCallingIdentity();
                     try {
-                        shell.mInternal.readState();
+                        shell.mInternal.readRecentAccesses();
+                        shell.mInternal.mAppOpsCheckingService.readState();
                         pw.println("Last settings read.");
                     } finally {
                         Binder.restoreCallingIdentity(token);
diff --git a/services/core/java/com/android/server/appop/PersistenceScheduler.java b/services/core/java/com/android/server/appop/PersistenceScheduler.java
deleted file mode 100644
index e50b658..0000000
--- a/services/core/java/com/android/server/appop/PersistenceScheduler.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * 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.server.appop;
-
-/**
- * Interface that allows callers to persist AppOpsService's internal state
- * to disk.
- */
-public interface PersistenceScheduler {
-
-    /**
-     * Schedules disk writes for appOpsService and it's internal states.
-     */
-    void scheduleWriteLocked();
-
-    /**
-     * Schedules fast disk writes for appOpsService and it's internal states.
-     */
-    void scheduleFastWriteLocked();
-
-}
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index c8d43e4..ffa5d20 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -563,6 +563,14 @@
                 }
             }
 
+            // Set the default subtitle if necessary.
+            if (promptInfo.isUseDefaultSubtitle()) {
+                if (TextUtils.isEmpty(promptInfo.getSubtitle())) {
+                    promptInfo.setSubtitle(getContext()
+                            .getString(R.string.biometric_dialog_default_subtitle));
+                }
+            }
+
             final long requestId = mRequestCounter.get();
             mHandler.post(() -> handleAuthenticate(
                     token, requestId, operationId, userId, receiver, opPackageName, promptInfo));
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index ec2e254..0318b24 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -488,31 +488,65 @@
      * migrated already.
      */
     private void migrateSyncJobNamespaceIfNeeded() {
-        if (mSyncStorageEngine.isJobNamespaceMigrated()) {
+        final boolean namespaceMigrated = mSyncStorageEngine.isJobNamespaceMigrated();
+        final boolean attributionFixed = mSyncStorageEngine.isJobAttributionFixed();
+        if (namespaceMigrated && attributionFixed) {
             return;
         }
         final JobScheduler jobSchedulerDefaultNamespace =
                 mContext.getSystemService(JobScheduler.class);
-        final List<JobInfo> pendingJobs = jobSchedulerDefaultNamespace.getAllPendingJobs();
-        // Wait until we've confirmed that all syncs have been migrated to the new namespace
-        // before we persist successful migration to our status file. This is done to avoid
+        if (!namespaceMigrated) {
+            final List<JobInfo> pendingJobs = jobSchedulerDefaultNamespace.getAllPendingJobs();
+            // Wait until we've confirmed that all syncs have been migrated to the new namespace
+            // before we persist successful migration to our status file. This is done to avoid
+            // internal consistency issues if the devices reboots right after SyncManager has
+            // done the migration on its side but before JobScheduler has finished persisting
+            // the updated jobs to disk. If JobScheduler hasn't persisted the update to disk,
+            // then nothing that happened afterwards should have been persisted either, so there's
+            // no concern over activity happening after the migration causing issues.
+            boolean allSyncsMigrated = true;
+            for (int i = pendingJobs.size() - 1; i >= 0; --i) {
+                final JobInfo job = pendingJobs.get(i);
+                final SyncOperation op = SyncOperation.maybeCreateFromJobExtras(job.getExtras());
+                if (op != null) {
+                    // This is a sync. Move it over to SyncManager's namespace.
+                    mJobScheduler.scheduleAsPackage(job,
+                            op.owningPackage, op.target.userId, op.wakeLockName());
+                    jobSchedulerDefaultNamespace.cancel(job.getId());
+                    allSyncsMigrated = false;
+                }
+            }
+            mSyncStorageEngine.setJobNamespaceMigrated(allSyncsMigrated);
+        }
+
+        // Fix attribution for any syncs that were previously scheduled using
+        // JobScheduler.schedule() instead of JobScheduler.scheduleAsPackage().
+        final List<JobInfo> namespacedJobs = LocalServices.getService(JobSchedulerInternal.class)
+                .getSystemScheduledOwnJobs(mJobScheduler.getNamespace());
+        // Wait until we've confirmed that all syncs have been proper attribution
+        // before we persist attribution state to our status file. This is done to avoid
         // internal consistency issues if the devices reboots right after SyncManager has
-        // done the migration on its side but before JobScheduler has finished persisting
+        // rescheduled the job on its side but before JobScheduler has finished persisting
         // the updated jobs to disk. If JobScheduler hasn't persisted the update to disk,
         // then nothing that happened afterwards should have been persisted either, so there's
         // no concern over activity happening after the migration causing issues.
-        boolean allSyncsMigrated = true;
-        for (int i = pendingJobs.size() - 1; i >= 0; --i) {
-            final JobInfo job = pendingJobs.get(i);
+        // This case is done to fix issues for a subset of test devices.
+        // TODO: remove this attribution check/fix code
+        boolean allSyncsAttributed = true;
+        for (int i = namespacedJobs.size() - 1; i >= 0; --i) {
+            final JobInfo job = namespacedJobs.get(i);
             final SyncOperation op = SyncOperation.maybeCreateFromJobExtras(job.getExtras());
             if (op != null) {
-                // This is a sync. Move it over to SyncManager's namespace.
-                mJobScheduler.schedule(job);
-                jobSchedulerDefaultNamespace.cancel(job.getId());
-                allSyncsMigrated = false;
+                // This is a sync. Make sure it's properly attributed to the app
+                // instead of the system.
+                // Since the job ID stays the same, scheduleAsPackage will replace the scheduled
+                // job, so we don't need to call cancel as well.
+                mJobScheduler.scheduleAsPackage(job,
+                        op.owningPackage, op.target.userId, op.wakeLockName());
+                allSyncsAttributed = false;
             }
         }
-        mSyncStorageEngine.setJobNamespaceMigrated(allSyncsMigrated);
+        mSyncStorageEngine.setJobAttributionFixed(allSyncsAttributed);
     }
 
     private synchronized void verifyJobScheduler() {
diff --git a/services/core/java/com/android/server/content/SyncStorageEngine.java b/services/core/java/com/android/server/content/SyncStorageEngine.java
index 9f3302d..b890bbd 100644
--- a/services/core/java/com/android/server/content/SyncStorageEngine.java
+++ b/services/core/java/com/android/server/content/SyncStorageEngine.java
@@ -173,6 +173,7 @@
     private volatile boolean mIsClockValid;
 
     private volatile boolean mIsJobNamespaceMigrated;
+    private volatile boolean mIsJobAttributionFixed;
 
     static {
         sAuthorityRenames = new HashMap<String, String>();
@@ -852,6 +853,20 @@
         return mIsJobNamespaceMigrated;
     }
 
+    void setJobAttributionFixed(boolean fixed) {
+        if (mIsJobAttributionFixed == fixed) {
+            return;
+        }
+        mIsJobAttributionFixed = fixed;
+        // This isn't urgent enough to write synchronously. Post it to the handler thread so
+        // SyncManager can move on with whatever it was doing.
+        mHandler.sendEmptyMessageDelayed(MSG_WRITE_STATUS, WRITE_STATUS_DELAY);
+    }
+
+    boolean isJobAttributionFixed() {
+        return mIsJobAttributionFixed;
+    }
+
     public Pair<Long, Long> getBackoff(EndPoint info) {
         synchronized (mAuthorities) {
             AuthorityInfo authority = getAuthorityLocked(info, "getBackoff");
@@ -2120,6 +2135,10 @@
                     mIsJobNamespaceMigrated =
                             proto.readBoolean(SyncStatusProto.IS_JOB_NAMESPACE_MIGRATED);
                     break;
+                case (int) SyncStatusProto.IS_JOB_ATTRIBUTION_FIXED:
+                    mIsJobAttributionFixed =
+                            proto.readBoolean(SyncStatusProto.IS_JOB_ATTRIBUTION_FIXED);
+                    break;
                 case ProtoInputStream.NO_MORE_FIELDS:
                     return;
             }
@@ -2389,6 +2408,7 @@
         }
 
         proto.write(SyncStatusProto.IS_JOB_NAMESPACE_MIGRATED, mIsJobNamespaceMigrated);
+        proto.write(SyncStatusProto.IS_JOB_ATTRIBUTION_FIXED, mIsJobAttributionFixed);
 
         proto.flush();
     }
diff --git a/services/core/java/com/android/server/dreams/DreamController.java b/services/core/java/com/android/server/dreams/DreamController.java
index d7306b7..66fe019 100644
--- a/services/core/java/com/android/server/dreams/DreamController.java
+++ b/services/core/java/com/android/server/dreams/DreamController.java
@@ -294,8 +294,6 @@
                         new int[] {ACTIVITY_TYPE_DREAM});
 
                 mListener.onDreamStopped(dream.mToken);
-            } else if (dream.mCanDoze && !mCurrentDream.mCanDoze) {
-                mListener.stopDozing(dream.mToken);
             }
 
         } finally {
@@ -343,7 +341,6 @@
      */
     public interface Listener {
         void onDreamStopped(Binder token);
-        void stopDozing(Binder token);
     }
 
     private final class DreamRecord implements DeathRecipient, ServiceConnection {
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index caa5036..7802b9d 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -500,12 +500,7 @@
         }
 
         synchronized (mLock) {
-            if (mCurrentDream == null) {
-                return;
-            }
-
-            final boolean sameDream = mCurrentDream.token == token;
-            if ((sameDream && mCurrentDream.isDozing) || (!sameDream && !mCurrentDream.isDozing)) {
+            if (mCurrentDream != null && mCurrentDream.token == token && mCurrentDream.isDozing) {
                 mCurrentDream.isDozing = false;
                 mDozeWakeLock.release();
                 mPowerManagerInternal.setDozeOverrideFromDreamManager(
@@ -671,6 +666,10 @@
 
         Slog.i(TAG, "Entering dreamland.");
 
+        if (mCurrentDream != null && mCurrentDream.isDozing) {
+            stopDozingInternal(mCurrentDream.token);
+        }
+
         mCurrentDream = new DreamRecord(name, userId, isPreviewMode, canDoze);
 
         if (!mCurrentDream.name.equals(mAmbientDisplayComponent)) {
@@ -776,11 +775,6 @@
                 }
             }
         }
-
-        @Override
-        public void stopDozing(Binder token) {
-            stopDozingInternal(token);
-        }
     };
 
     private final ContentObserver mDozeEnabledObserver = new ContentObserver(null) {
diff --git a/services/core/java/com/android/server/hdmi/Constants.java b/services/core/java/com/android/server/hdmi/Constants.java
index 6d1af3b..e21ec72 100644
--- a/services/core/java/com/android/server/hdmi/Constants.java
+++ b/services/core/java/com/android/server/hdmi/Constants.java
@@ -627,11 +627,14 @@
     })
     @interface HpdSignalType {}
 
-    static final String DEVICE_CONFIG_FEATURE_FLAG_SOUNDBAR_MODE = "soundbar_mode";
+    static final String DEVICE_CONFIG_FEATURE_FLAG_SOUNDBAR_MODE = "enable_soundbar_mode";
     static final String DEVICE_CONFIG_FEATURE_FLAG_ENABLE_EARC_TX = "enable_earc_tx";
+    static final String DEVICE_CONFIG_FEATURE_FLAG_TRANSITION_ARC_TO_EARC_TX =
+            "transition_arc_to_earc_tx";
     @StringDef({
             DEVICE_CONFIG_FEATURE_FLAG_SOUNDBAR_MODE,
-            DEVICE_CONFIG_FEATURE_FLAG_ENABLE_EARC_TX
+            DEVICE_CONFIG_FEATURE_FLAG_ENABLE_EARC_TX,
+            DEVICE_CONFIG_FEATURE_FLAG_TRANSITION_ARC_TO_EARC_TX
     })
     @interface FeatureFlag {}
 
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 740d2a3..3563938 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -458,6 +458,9 @@
     private boolean mEarcTxFeatureFlagEnabled = false;
 
     @ServiceThreadOnly
+    private boolean mTransitionFromArcToEarcTxEnabled = false;
+
+    @ServiceThreadOnly
     private int mActivePortId = Constants.INVALID_PORT_ID;
 
     // Set to true while the input change by MHL is allowed.
@@ -675,6 +678,8 @@
                 Constants.DEVICE_CONFIG_FEATURE_FLAG_SOUNDBAR_MODE, false);
         mEarcTxFeatureFlagEnabled = mDeviceConfig.getBoolean(
                 Constants.DEVICE_CONFIG_FEATURE_FLAG_ENABLE_EARC_TX, false);
+        mTransitionFromArcToEarcTxEnabled = mDeviceConfig.getBoolean(
+                Constants.DEVICE_CONFIG_FEATURE_FLAG_TRANSITION_ARC_TO_EARC_TX, false);
 
         synchronized (mLock) {
             mEarcEnabled = (mHdmiCecConfig.getIntValue(
@@ -886,6 +891,16 @@
                                 ? SOUNDBAR_MODE_ENABLED : SOUNDBAR_MODE_DISABLED);
                     }
                 }, mServiceThreadExecutor);
+
+        mDeviceConfig.addOnPropertiesChangedListener(getContext().getMainExecutor(),
+                new DeviceConfig.OnPropertiesChangedListener() {
+                    @Override
+                    public void onPropertiesChanged(DeviceConfig.Properties properties) {
+                        mTransitionFromArcToEarcTxEnabled = properties.getBoolean(
+                                Constants.DEVICE_CONFIG_FEATURE_FLAG_TRANSITION_ARC_TO_EARC_TX,
+                                false);
+                    }
+                });
     }
     /** Returns true if the device screen is off */
     boolean isScreenOff() {
@@ -1618,6 +1633,11 @@
     }
 
     void enableAudioReturnChannel(int portId, boolean enabled) {
+        if (!mTransitionFromArcToEarcTxEnabled && enabled && mEarcController != null) {
+            // If the feature flag is set to false, prevent eARC from establishing if ARC is already
+            // established.
+            setEarcEnabledInHal(false, false);
+        }
         mCecController.enableAudioReturnChannel(portId, enabled);
     }
 
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 233e285..c7e4cd2 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -4591,6 +4591,13 @@
             Slog.w(TAG, "Ignoring setInputMethod of uid " + Binder.getCallingUid()
                     + " token: " + token);
             return;
+        } else {
+            // Called with current IME's token.
+            if (mMethodMap.get(id) != null
+                    && mSettings.getEnabledInputMethodListWithFilterLocked(
+                            (info) -> info.getId().equals(id)).isEmpty()) {
+                throw new IllegalStateException("Requested IME is not enabled: " + id);
+            }
         }
 
         final long ident = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/location/altitude/AltitudeService.java b/services/core/java/com/android/server/location/altitude/AltitudeService.java
new file mode 100644
index 0000000..b321e4d
--- /dev/null
+++ b/services/core/java/com/android/server/location/altitude/AltitudeService.java
@@ -0,0 +1,94 @@
+/*
+ * 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.location.altitude;
+
+import android.content.Context;
+import android.frameworks.location.altitude.AddMslAltitudeToLocationRequest;
+import android.frameworks.location.altitude.AddMslAltitudeToLocationResponse;
+import android.frameworks.location.altitude.IAltitudeService;
+import android.location.Location;
+import android.location.altitude.AltitudeConverter;
+import android.os.RemoteException;
+
+import com.android.server.SystemService;
+
+import java.io.IOException;
+
+/**
+ * Manages altitude information exchange through the HAL, e.g., geoid height requests such that
+ * vendors can perform altitude conversions.
+ *
+ * @hide
+ */
+public class AltitudeService extends IAltitudeService.Stub {
+
+    private final AltitudeConverter mAltitudeConverter = new AltitudeConverter();
+    private final Context mContext;
+
+    /** @hide */
+    public AltitudeService(Context context) {
+        mContext = context;
+    }
+
+    @Override
+    public AddMslAltitudeToLocationResponse addMslAltitudeToLocation(
+            AddMslAltitudeToLocationRequest request) throws RemoteException {
+        Location location = new Location("");
+        location.setLatitude(request.latitudeDegrees);
+        location.setLongitude(request.longitudeDegrees);
+        location.setAltitude(request.altitudeMeters);
+        location.setVerticalAccuracyMeters(request.verticalAccuracyMeters);
+        try {
+            mAltitudeConverter.addMslAltitudeToLocation(mContext, location);
+        } catch (IOException e) {
+            throw new RemoteException(e);
+        }
+
+        AddMslAltitudeToLocationResponse response = new AddMslAltitudeToLocationResponse();
+        response.mslAltitudeMeters = location.getMslAltitudeMeters();
+        response.mslAltitudeAccuracyMeters = location.getMslAltitudeAccuracyMeters();
+        return response;
+    }
+
+    @Override
+    public String getInterfaceHash() {
+        return IAltitudeService.HASH;
+    }
+
+    @Override
+    public int getInterfaceVersion() {
+        return IAltitudeService.VERSION;
+    }
+
+    /** @hide */
+    public static class Lifecycle extends SystemService {
+
+        private static final String SERVICE_NAME = IAltitudeService.DESCRIPTOR + "/default";
+
+        private AltitudeService mService;
+
+        public Lifecycle(Context context) {
+            super(context);
+        }
+
+        @Override
+        public void onStart() {
+            mService = new AltitudeService(getContext());
+            publishBinderService(SERVICE_NAME, mService);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 1457bff..149a915 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -2553,7 +2553,7 @@
      */
     @NonNull
     public RemoteLockscreenValidationResult
-            validateRemoteLockScreen2(@NonNull byte[] encryptedCredential) {
+            validateRemoteLockscreen(@NonNull byte[] encryptedCredential) {
         return mRecoverableKeyStoreManager.validateRemoteLockscreen(encryptedCredential, this);
     }
 
@@ -3439,10 +3439,10 @@
                     Slog.w(TAG, "Querying password metrics for unified challenge profile: "
                             + userHandle);
                 }
+                return LockSettingsService.this.getUserPasswordMetrics(userHandle);
             } finally {
                 Binder.restoreCallingIdentity(identity);
             }
-            return LockSettingsService.this.getUserPasswordMetrics(userHandle);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
index 33dc7ef..6bf64eb 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
@@ -1126,11 +1126,10 @@
     }
 
     private void checkVerifyRemoteLockscreenPermission() {
-        // TODO(b/254335492): Check new system permission
         mContext.enforceCallingOrSelfPermission(
-                Manifest.permission.RECOVER_KEYSTORE,
+                Manifest.permission.CHECK_REMOTE_LOCKSCREEN,
                 "Caller " + Binder.getCallingUid()
-                        + " doesn't have verifyRemoteLockscreen permission.");
+                        + " doesn't have CHECK_REMOTE_LOCKSCREEN permission.");
         int userId = UserHandle.getCallingUserId();
         int uid = Binder.getCallingUid();
         mCleanupManager.registerRecoveryAgent(userId, uid);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 0aa822b..94d5d53 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -114,6 +114,7 @@
 import static android.service.notification.NotificationListenerService.TRIM_LIGHT;
 import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
 
+import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.ALLOW_DISMISS_ONGOING;
 import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE;
 import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES;
 import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES;
@@ -271,6 +272,7 @@
 import com.android.internal.app.IAppOpsService;
 import com.android.internal.compat.IPlatformCompat;
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
+import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
 import com.android.internal.logging.InstanceId;
 import com.android.internal.logging.InstanceIdSequence;
 import com.android.internal.logging.MetricsLogger;
@@ -577,11 +579,11 @@
     private float mInCallNotificationVolume;
     private Binder mCallNotificationToken = null;
 
-    private static final boolean ONGOING_DISMISSAL = SystemProperties.getBoolean(
-            "persist.sysui.notification.ongoing_dismissal", true);
     @VisibleForTesting
     protected boolean mSystemExemptFromDismissal = false;
 
+    private SystemUiSystemPropertiesFlags.FlagResolver mFlagResolver;
+
     // used as a mutex for access to all active notifications & listeners
     final Object mNotificationLock = new Object();
     @GuardedBy("mNotificationLock")
@@ -1208,7 +1210,8 @@
                 }
             }
 
-            int mustNotHaveFlags = ONGOING_DISMISSAL ? FLAG_NO_DISMISS : FLAG_ONGOING_EVENT;
+            int mustNotHaveFlags = mFlagResolver.isEnabled(ALLOW_DISMISS_ONGOING)
+                    ? FLAG_NO_DISMISS : FLAG_ONGOING_EVENT;
             cancelNotification(callingUid, callingPid, pkg, tag, id,
                     /* mustHaveFlags= */ 0,
                     /* mustNotHaveFlags= */ mustNotHaveFlags,
@@ -2219,7 +2222,8 @@
             TelephonyManager telephonyManager, ActivityManagerInternal ami,
             MultiRateLimiter toastRateLimiter, PermissionHelper permissionHelper,
             UsageStatsManagerInternal usageStatsManagerInternal,
-            TelecomManager telecomManager, NotificationChannelLogger channelLogger) {
+            TelecomManager telecomManager, NotificationChannelLogger channelLogger,
+            SystemUiSystemPropertiesFlags.FlagResolver flagResolver) {
         mHandler = handler;
         Resources resources = getContext().getResources();
         mMaxPackageEnqueueRate = Settings.Global.getFloat(getContext().getContentResolver(),
@@ -2417,6 +2421,8 @@
         mMsgPkgsAllowedAsConvos = Set.of(getStringArrayResource(
                 com.android.internal.R.array.config_notificationMsgPkgsAllowedAsConvos));
 
+        mFlagResolver = flagResolver;
+
         mStatsManager = statsManager;
 
         mToastRateLimiter = toastRateLimiter;
@@ -2548,7 +2554,7 @@
                         AppGlobals.getPermissionManager()),
                 LocalServices.getService(UsageStatsManagerInternal.class),
                 getContext().getSystemService(TelecomManager.class),
-                new NotificationChannelLoggerImpl());
+                new NotificationChannelLoggerImpl(), SystemUiSystemPropertiesFlags.getResolver());
 
         publishBinderService(Context.NOTIFICATION_SERVICE, mService, /* allowIsolated= */ false,
                 DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PRIORITY_NORMAL);
@@ -6516,7 +6522,7 @@
 
         // Fix the notification as best we can.
         try {
-            fixNotification(notification, pkg, tag, id, userId);
+            fixNotification(notification, pkg, tag, id, userId, notificationUid);
         } catch (Exception e) {
             if (notification.isForegroundService()) {
                 throw new SecurityException("Invalid FGS notification", e);
@@ -6694,14 +6700,15 @@
 
     @VisibleForTesting
     protected void fixNotification(Notification notification, String pkg, String tag, int id,
-            int userId) throws NameNotFoundException, RemoteException {
+            @UserIdInt int userId, int notificationUid) throws NameNotFoundException,
+            RemoteException {
         final ApplicationInfo ai = mPackageManagerClient.getApplicationInfoAsUser(
                 pkg, PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
                 (userId == UserHandle.USER_ALL) ? USER_SYSTEM : userId);
         Notification.addFieldsFromContext(ai, notification);
 
         // Only notifications that can be non-dismissible can have the flag FLAG_NO_DISMISS
-        if (ONGOING_DISMISSAL) {
+        if (mFlagResolver.isEnabled(ALLOW_DISMISS_ONGOING)) {
             if (((notification.flags & FLAG_ONGOING_EVENT) > 0)
                     && canBeNonDismissible(ai, notification)) {
                 notification.flags |= FLAG_NO_DISMISS;
@@ -6710,17 +6717,31 @@
             }
         }
 
-        int canColorize = mPackageManagerClient.checkPermission(
-                android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, pkg);
+        int canColorize = getContext().checkPermission(
+                android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, -1, notificationUid);
+
         if (canColorize == PERMISSION_GRANTED) {
             notification.flags |= Notification.FLAG_CAN_COLORIZE;
         } else {
             notification.flags &= ~Notification.FLAG_CAN_COLORIZE;
         }
 
+        if (notification.extras.getBoolean(Notification.EXTRA_ALLOW_DURING_SETUP, false)) {
+            int hasShowDuringSetupPerm = getContext().checkPermission(
+                    android.Manifest.permission.NOTIFICATION_DURING_SETUP, -1, notificationUid);
+            if (hasShowDuringSetupPerm != PERMISSION_GRANTED) {
+                notification.extras.remove(Notification.EXTRA_ALLOW_DURING_SETUP);
+                if (DBG) {
+                    Slog.w(TAG, "warning: pkg " + pkg + " attempting to show during setup"
+                            + " without holding perm "
+                            + Manifest.permission.NOTIFICATION_DURING_SETUP);
+                }
+            }
+        }
+
         if (notification.fullScreenIntent != null && ai.targetSdkVersion >= Build.VERSION_CODES.Q) {
-            int fullscreenIntentPermission = mPackageManagerClient.checkPermission(
-                    android.Manifest.permission.USE_FULL_SCREEN_INTENT, pkg);
+            int fullscreenIntentPermission = getContext().checkPermission(
+                    android.Manifest.permission.USE_FULL_SCREEN_INTENT, -1, notificationUid);
             if (fullscreenIntentPermission != PERMISSION_GRANTED) {
                 notification.fullScreenIntent = null;
                 Slog.w(TAG, "Package " + pkg +
@@ -6765,8 +6786,8 @@
 
         // Ensure MediaStyle has correct permissions for remote device extras
         if (notification.isStyle(Notification.MediaStyle.class)) {
-            int hasMediaContentControlPermission = mPackageManager.checkPermission(
-                    android.Manifest.permission.MEDIA_CONTENT_CONTROL, pkg, userId);
+            int hasMediaContentControlPermission = getContext().checkPermission(
+                    android.Manifest.permission.MEDIA_CONTENT_CONTROL, -1, notificationUid);
             if (hasMediaContentControlPermission != PERMISSION_GRANTED) {
                 notification.extras.remove(Notification.EXTRA_MEDIA_REMOTE_DEVICE);
                 notification.extras.remove(Notification.EXTRA_MEDIA_REMOTE_ICON);
@@ -6780,8 +6801,8 @@
 
         // Ensure only allowed packages have a substitute app name
         if (notification.extras.containsKey(Notification.EXTRA_SUBSTITUTE_APP_NAME)) {
-            int hasSubstituteAppNamePermission = mPackageManager.checkPermission(
-                    permission.SUBSTITUTE_NOTIFICATION_APP_NAME, pkg, userId);
+            int hasSubstituteAppNamePermission = getContext().checkPermission(
+                    permission.SUBSTITUTE_NOTIFICATION_APP_NAME, -1, notificationUid);
             if (hasSubstituteAppNamePermission != PERMISSION_GRANTED) {
                 notification.extras.remove(Notification.EXTRA_SUBSTITUTE_APP_NAME);
                 if (DBG) {
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index 062f0fc..d471c8a 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -301,7 +301,7 @@
                     && shellPkgName.equals(overlayInfo.packageName));
 
             initIfNeeded();
-            onSwitchUser(UserHandle.USER_SYSTEM);
+            onStartUser(UserHandle.USER_SYSTEM);
 
             publishBinderService(Context.OVERLAY_SERVICE, mService);
             publishLocalService(OverlayManagerService.class, this);
@@ -324,7 +324,7 @@
                 final UserInfo userInfo = users.get(i);
                 if (!userInfo.supportsSwitchTo() && userInfo.id != UserHandle.USER_SYSTEM) {
                     // Initialize any users that can't be switched to, as their state would
-                    // never be setup in onSwitchUser(). We will switch to the system user right
+                    // never be setup in onStartUser(). We will switch to the system user right
                     // after this, and its state will be setup there.
                     updatePackageManagerLocked(mImpl.updateOverlaysForUser(users.get(i).id));
                 }
@@ -333,13 +333,13 @@
     }
 
     @Override
-    public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) {
-        onSwitchUser(to.getUserIdentifier());
+    public void onUserStarting(TargetUser user) {
+        onStartUser(user.getUserIdentifier());
     }
 
-    private void onSwitchUser(@UserIdInt int newUserId) {
+    private void onStartUser(@UserIdInt int newUserId) {
         try {
-            traceBegin(TRACE_TAG_RRO, "OMS#onSwitchUser " + newUserId);
+            traceBegin(TRACE_TAG_RRO, "OMS#onStartUser " + newUserId);
             // ensure overlays in the settings are up-to-date, and propagate
             // any asset changes to the rest of the system
             synchronized (mLock) {
diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java
index de37080..d8bfa59 100644
--- a/services/core/java/com/android/server/pm/DexOptHelper.java
+++ b/services/core/java/com/android/server/pm/DexOptHelper.java
@@ -40,8 +40,10 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.AppGlobals;
+import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.ResolveInfo;
 import android.content.pm.SharedLibraryInfo;
 import android.content.pm.dex.ArtManager;
@@ -1019,7 +1021,15 @@
                 pm.getDexOptHelper().new DexoptDoneHandler());
         LocalManagerRegistry.addManager(ArtManagerLocal.class, artManager);
 
-        artManager.scheduleBackgroundDexoptJob();
+        // Schedule the background job when boot is complete. This decouples us from when
+        // JobSchedulerService is initialized.
+        systemContext.registerReceiver(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                context.unregisterReceiver(this);
+                artManager.scheduleBackgroundDexoptJob();
+            }
+        }, new IntentFilter(Intent.ACTION_BOOT_COMPLETED));
     }
 
     /**
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 97ee3c5..382be45 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -189,7 +189,6 @@
 import com.android.permission.persistence.RuntimePermissionsPersistence;
 import com.android.server.EventLogTags;
 import com.android.server.FgThread;
-import com.android.server.IntentResolver;
 import com.android.server.LocalManagerRegistry;
 import com.android.server.LocalServices;
 import com.android.server.LockGuard;
@@ -4775,7 +4774,7 @@
                 ArraySet<CrossProfileIntentFilter> set =
                         new ArraySet<>(resolver.filterSet());
                 for (CrossProfileIntentFilter filter : set) {
-                    if (IntentResolver.filterEquals(filter.mFilter, intentFilter)
+                    if (IntentFilter.filterEquals(filter.mFilter, intentFilter)
                             && filter.getOwnerPackage().equals(ownerPackage)
                             && filter.getTargetUserId() == targetUserId
                             && filter.getFlags() == flags) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 002585f..5cfe6de 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -3123,12 +3123,7 @@
                 translateUserId(userId, UserHandle.USER_NULL, "runSetUserRestriction");
         final IUserManager um = IUserManager.Stub.asInterface(
                 ServiceManager.getService(Context.USER_SERVICE));
-        try {
-            um.setUserRestriction(restriction, value, translatedUserId);
-        } catch (IllegalArgumentException e) {
-            getErrPrintWriter().println(e.getMessage());
-            return 1;
-        }
+        um.setUserRestriction(restriction, value, translatedUserId);
         return 0;
     }
 
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 7e7205d..6541b40 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -98,7 +98,6 @@
 import com.android.modules.utils.TypedXmlSerializer;
 import com.android.permission.persistence.RuntimePermissionsPersistence;
 import com.android.permission.persistence.RuntimePermissionsState;
-import com.android.server.IntentResolver;
 import com.android.server.LocalServices;
 import com.android.server.backup.PreferredActivityBackupHelper;
 import com.android.server.pm.Installer.InstallerException;
@@ -6307,7 +6306,7 @@
         boolean changed = false;
         while (it.hasNext()) {
             PersistentPreferredActivity ppa = it.next();
-            if (IntentResolver.filterEquals(ppa.getIntentFilter(), filter)) {
+            if (IntentFilter.filterEquals(ppa.getIntentFilter(), filter)) {
                 ppir.removeFilter(ppa);
                 changed = true;
                 break;
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index ce7dc5b..372b580 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -2810,8 +2810,9 @@
         }
 
         if (!userExists(userId)) {
-            throw new IllegalArgumentException("Cannot set user restriction. "
-                    + "User with this id does not exist");
+            Slogf.w(LOG_TAG, "Cannot set user restriction %s. User with id %d does not exist",
+                    key, userId);
+            return;
         }
         synchronized (mRestrictionsLock) {
             // Note we can't modify Bundles stored in mBaseUserRestrictions directly, so create
diff --git a/services/core/java/com/android/server/pm/UserManagerServiceShellCommand.java b/services/core/java/com/android/server/pm/UserManagerServiceShellCommand.java
index 165b009..eed2a78 100644
--- a/services/core/java/com/android/server/pm/UserManagerServiceShellCommand.java
+++ b/services/core/java/com/android/server/pm/UserManagerServiceShellCommand.java
@@ -121,6 +121,9 @@
         pw.println("    If the display option is not set, it uses the user's context to check");
         pw.println("    (so it emulates what apps would get from UserManager.isUserVisible())");
         pw.println();
+        pw.println("  get-main-user ");
+        pw.println("    Displays main user id or message if there is no main user");
+        pw.println();
     }
 
     @Override
@@ -145,6 +148,8 @@
                     return runIsVisibleBackgroundUserOnDefaultDisplaySupported();
                 case "is-user-visible":
                     return runIsUserVisible();
+                case "get-main-user":
+                    return runGetMainUserId();
                 default:
                     return handleDefaultCommands(cmd);
             }
@@ -516,6 +521,17 @@
         return 0;
     }
 
+    private int runGetMainUserId() {
+        PrintWriter pw = getOutPrintWriter();
+        final int mainUserId = mService.getMainUserId();
+        if (mainUserId == UserHandle.USER_NULL) {
+            pw.println("Couldn't get main user.");
+            return 1;
+        }
+        pw.println("Main user id: " + mainUserId);
+        return 0;
+    }
+
     /**
      * Gets the {@link UserManager} associated with the context of the given user.
      */
diff --git a/services/core/java/com/android/server/pm/UserVisibilityMediator.java b/services/core/java/com/android/server/pm/UserVisibilityMediator.java
index d5cc7ca..3f7502b 100644
--- a/services/core/java/com/android/server/pm/UserVisibilityMediator.java
+++ b/services/core/java/com/android/server/pm/UserVisibilityMediator.java
@@ -330,6 +330,12 @@
                         ? USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE
                         : USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE;
             }
+        } else if (mUsersAssignedToDisplayOnStart != null
+                && isUserAssignedToDisplayOnStartLocked(userId, displayId)) {
+            if (DBG) {
+                Slogf.d(TAG, "full user %d is already visible on display %d", userId, displayId);
+            }
+            return USER_ASSIGNMENT_RESULT_SUCCESS_ALREADY_VISIBLE;
         }
 
         return foreground || displayId != DEFAULT_DISPLAY
@@ -403,7 +409,15 @@
             return SECONDARY_DISPLAY_MAPPING_NOT_NEEDED;
         }
 
-        // Check if display is available
+        if (mUsersAssignedToDisplayOnStart == null) {
+            // Should never have reached this point
+            Slogf.wtf(TAG, "canAssignUserToDisplayLocked(%d, %d, %d, %d) is trying to check "
+                    + "mUsersAssignedToDisplayOnStart when it's not set",
+                    userId, profileGroupId, userStartMode, displayId);
+            return SECONDARY_DISPLAY_MAPPING_FAILED;
+        }
+
+        // Check if display is available and user is not assigned to any display
         for (int i = 0; i < mUsersAssignedToDisplayOnStart.size(); i++) {
             int assignedUserId = mUsersAssignedToDisplayOnStart.keyAt(i);
             int assignedDisplayId = mUsersAssignedToDisplayOnStart.valueAt(i);
@@ -601,6 +615,23 @@
         return visible;
     }
 
+    @GuardedBy("mLock")
+    private boolean isUserAssignedToDisplayOnStartLocked(@UserIdInt int userId, int displayId) {
+        if (mUsersAssignedToDisplayOnStart == null) {
+            // Shouldn't have been called in this case
+            Slogf.wtf(TAG, "isUserAssignedToDisplayOnStartLocked(%d, %d): called when "
+                    + "mUsersAssignedToDisplayOnStart is null", userId, displayId);
+            return false;
+        }
+        boolean isIt = displayId != INVALID_DISPLAY
+                && mUsersAssignedToDisplayOnStart.get(userId, INVALID_DISPLAY) == displayId;
+        if (VERBOSE) {
+            Slogf.v(TAG, "isUserAssignedToDisplayOnStartLocked(%d, %d): %b", userId, displayId,
+                    isIt);
+        }
+        return isIt;
+    }
+
     /**
      * See {@link UserManagerInternal#isUserVisible(int, int)}.
      */
diff --git a/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java b/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java
index 2a65a01..8641b41 100644
--- a/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java
+++ b/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
 import android.app.AlarmManager;
 import android.app.IActivityManager;
 import android.app.IUidObserver;
@@ -34,6 +35,7 @@
 import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.server.LocalServices;
 import com.android.server.PermissionThread;
 
 /**
@@ -50,6 +52,7 @@
 
     private final @NonNull Context mContext;
     private final @NonNull IActivityManager mIActivityManager;
+    private final @NonNull ActivityManagerInternal mActivityManagerInternal;
     private final @NonNull AlarmManager mAlarmManager;
     private final @NonNull PermissionControllerManager mPermissionControllerManager;
 
@@ -80,6 +83,7 @@
     OneTimePermissionUserManager(@NonNull Context context) {
         mContext = context;
         mIActivityManager = ActivityManager.getService();
+        mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
         mAlarmManager = context.getSystemService(AlarmManager.class);
         mPermissionControllerManager = new PermissionControllerManager(
                 mContext, PermissionThread.getHandler());
@@ -243,12 +247,7 @@
         }
 
         private int getCurrentState() {
-            try {
-                return getStateFromProcState(mIActivityManager.getUidProcessState(mUid, null));
-            } catch (RemoteException e) {
-                Log.e(LOG_TAG, "Couldn't check uid proc state", e);
-            }
-            return STATE_GONE;
+            return getStateFromProcState(mActivityManagerInternal.getUidProcessState(mUid));
         }
 
         private int getStateFromProcState(int procState) {
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index c5258b24..7f1679a 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -38,6 +38,7 @@
 import static android.view.KeyEvent.KEYCODE_DPAD_DOWN;
 import static android.view.KeyEvent.KEYCODE_HOME;
 import static android.view.KeyEvent.KEYCODE_POWER;
+import static android.view.KeyEvent.KEYCODE_STEM_PRIMARY;
 import static android.view.KeyEvent.KEYCODE_UNKNOWN;
 import static android.view.KeyEvent.KEYCODE_VOLUME_DOWN;
 import static android.view.KeyEvent.KEYCODE_VOLUME_UP;
@@ -128,6 +129,7 @@
 import android.media.IAudioService;
 import android.media.session.MediaSessionLegacyHelper;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.DeviceIdleManager;
 import android.os.FactoryTest;
@@ -2248,6 +2250,21 @@
                             cancelPendingScreenshotChordAction();
                         }
                     });
+            if (mHasFeatureWatch) {
+                mKeyCombinationManager.addRule(
+                        new TwoKeysCombinationRule(KEYCODE_POWER, KEYCODE_STEM_PRIMARY) {
+                            @Override
+                            void execute() {
+                                mPowerKeyHandled = true;
+                                interceptScreenshotChord(SCREENSHOT_KEY_CHORD,
+                                        getScreenshotChordLongPressDelay());
+                            }
+                            @Override
+                            void cancel() {
+                                cancelPendingScreenshotChordAction();
+                            }
+                        });
+            }
         }
 
         mKeyCombinationManager.addRule(
diff --git a/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java b/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java
index be90530..90b6f95 100644
--- a/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java
@@ -126,7 +126,7 @@
                     "Turning off vibrator " + getVibratorId());
         }
         controller.off();
-        getVibration().stats().reportVibratorOff();
+        getVibration().stats.reportVibratorOff();
         mPendingVibratorOffDeadline = 0;
     }
 
@@ -136,7 +136,7 @@
                     "Amplitude changed on vibrator " + getVibratorId() + " to " + amplitude);
         }
         controller.setAmplitude(amplitude);
-        getVibration().stats().reportSetAmplitude();
+        getVibration().stats.reportSetAmplitude();
     }
 
     /**
@@ -170,7 +170,7 @@
             // Count the loops that were played.
             int loopSize = effectSize - repeatIndex;
             int loopSegmentsPlayed = nextSegmentIndex - repeatIndex;
-            getVibration().stats().reportRepetition(loopSegmentsPlayed / loopSize);
+            getVibration().stats.reportRepetition(loopSegmentsPlayed / loopSize);
             nextSegmentIndex = repeatIndex + ((nextSegmentIndex - effectSize) % loopSize);
         }
         Step nextStep = conductor.nextVibrateStep(nextStartTime, controller, effect,
diff --git a/services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java b/services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java
index 545ec5b..940bd08 100644
--- a/services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java
@@ -73,7 +73,7 @@
                     primitives.toArray(new PrimitiveSegment[primitives.size()]);
             long vibratorOnResult = controller.on(primitivesArray, getVibration().id);
             handleVibratorOnResult(vibratorOnResult);
-            getVibration().stats().reportComposePrimitives(vibratorOnResult, primitivesArray);
+            getVibration().stats.reportComposePrimitives(vibratorOnResult, primitivesArray);
 
             // The next start and off times will be calculated from mVibratorOnResult.
             return nextSteps(/* segmentsPlayed= */ primitives.size());
diff --git a/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java b/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java
index 8bfa2c3..7100ffd 100644
--- a/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java
@@ -72,7 +72,7 @@
             RampSegment[] pwlesArray = pwles.toArray(new RampSegment[pwles.size()]);
             long vibratorOnResult = controller.on(pwlesArray, getVibration().id);
             handleVibratorOnResult(vibratorOnResult);
-            getVibration().stats().reportComposePwle(vibratorOnResult, pwlesArray);
+            getVibration().stats.reportComposePwle(vibratorOnResult, pwlesArray);
 
             // The next start and off times will be calculated from mVibratorOnResult.
             return nextSteps(/* segmentsPlayed= */ pwles.size());
diff --git a/services/core/java/com/android/server/vibrator/FinishSequentialEffectStep.java b/services/core/java/com/android/server/vibrator/FinishSequentialEffectStep.java
index bbbca02..c9683d9 100644
--- a/services/core/java/com/android/server/vibrator/FinishSequentialEffectStep.java
+++ b/services/core/java/com/android/server/vibrator/FinishSequentialEffectStep.java
@@ -51,7 +51,8 @@
                 Slog.d(VibrationThread.TAG,
                         "FinishSequentialEffectStep for effect #" + startedStep.currentIndex);
             }
-            conductor.vibratorManagerHooks.noteVibratorOff(conductor.getVibration().uid);
+            conductor.vibratorManagerHooks.noteVibratorOff(
+                    conductor.getVibration().callerInfo.uid);
             Step nextStep = startedStep.nextStep();
             return nextStep == null ? VibrationStepConductor.EMPTY_STEP_LIST
                     : Arrays.asList(nextStep);
@@ -68,6 +69,7 @@
 
     @Override
     public void cancelImmediately() {
-        conductor.vibratorManagerHooks.noteVibratorOff(conductor.getVibration().uid);
+        conductor.vibratorManagerHooks.noteVibratorOff(
+                conductor.getVibration().callerInfo.uid);
     }
 }
diff --git a/services/core/java/com/android/server/vibrator/HalVibration.java b/services/core/java/com/android/server/vibrator/HalVibration.java
index 53f04d7..87f189a 100644
--- a/services/core/java/com/android/server/vibrator/HalVibration.java
+++ b/services/core/java/com/android/server/vibrator/HalVibration.java
@@ -16,6 +16,7 @@
 
 package com.android.server.vibrator;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.CombinedVibration;
 import android.os.IBinder;
@@ -35,13 +36,6 @@
  */
 final class HalVibration extends Vibration {
 
-    public final VibrationAttributes attrs;
-    public final long id;
-    public final int uid;
-    public final int displayId;
-    public final String opPkg;
-    public final String reason;
-    public final IBinder token;
     public final SparseArray<VibrationEffect> mFallbacks = new SparseArray<>();
 
     /** The actual effect to be played. */
@@ -58,29 +52,15 @@
     /** Vibration status. */
     private Vibration.Status mStatus;
 
-    /** Vibration runtime stats. */
-    private final VibrationStats mStats = new VibrationStats();
-
     /** A {@link CountDownLatch} to enable waiting for completion. */
     private final CountDownLatch mCompletionLatch = new CountDownLatch(1);
 
-    HalVibration(IBinder token, int id, CombinedVibration effect,
-            VibrationAttributes attrs, int uid, int displayId, String opPkg, String reason) {
-        this.token = token;
+    HalVibration(@NonNull IBinder token, CombinedVibration effect, @NonNull CallerInfo callerInfo) {
+        super(token, callerInfo);
         this.mEffect = effect;
-        this.id = id;
-        this.attrs = attrs;
-        this.uid = uid;
-        this.displayId = displayId;
-        this.opPkg = opPkg;
-        this.reason = reason;
         mStatus = Vibration.Status.RUNNING;
     }
 
-    VibrationStats stats() {
-        return mStats;
-    }
-
     /**
      * Set the {@link Status} of this vibration and reports the current system time as this
      * vibration end time, for debugging purposes.
@@ -94,7 +74,7 @@
             return;
         }
         mStatus = info.status;
-        mStats.reportEnded(info.endedByUid, info.endedByUsage);
+        stats.reportEnded(info.endedBy);
         mCompletionLatch.countDown();
     }
 
@@ -190,8 +170,8 @@
      * Return {@link Vibration.DebugInfo} with read-only debug information about this vibration.
      */
     public Vibration.DebugInfo getDebugInfo() {
-        return new Vibration.DebugInfo(mStatus, mStats, mEffect, mOriginalEffect, /* scale= */ 0,
-                attrs, uid, displayId, opPkg, reason);
+        return new Vibration.DebugInfo(mStatus, stats, mEffect, mOriginalEffect, /* scale= */ 0,
+                callerInfo);
     }
 
     /** Return {@link VibrationStats.StatsInfo} with read-only metrics about this vibration. */
@@ -200,7 +180,8 @@
                 ? FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__REPEATED
                 : FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__SINGLE;
         return new VibrationStats.StatsInfo(
-                uid, vibrationType, attrs.getUsage(), mStatus, mStats, completionUptimeMillis);
+                callerInfo.uid, vibrationType, callerInfo.attrs.getUsage(), mStatus,
+                stats, completionUptimeMillis);
     }
 
     /**
@@ -212,8 +193,9 @@
      * pipeline very short vibrations together, regardless of the flag.
      */
     public boolean canPipelineWith(HalVibration vib) {
-        return uid == vib.uid && attrs.isFlagSet(VibrationAttributes.FLAG_PIPELINED_EFFECT)
-                && vib.attrs.isFlagSet(VibrationAttributes.FLAG_PIPELINED_EFFECT)
+        return callerInfo.uid == vib.callerInfo.uid && callerInfo.attrs.isFlagSet(
+                VibrationAttributes.FLAG_PIPELINED_EFFECT)
+                && vib.callerInfo.attrs.isFlagSet(VibrationAttributes.FLAG_PIPELINED_EFFECT)
                 && !isRepeating();
     }
 }
diff --git a/services/core/java/com/android/server/vibrator/InputDeviceDelegate.java b/services/core/java/com/android/server/vibrator/InputDeviceDelegate.java
index 21ba874..4e58b9a 100644
--- a/services/core/java/com/android/server/vibrator/InputDeviceDelegate.java
+++ b/services/core/java/com/android/server/vibrator/InputDeviceDelegate.java
@@ -21,7 +21,6 @@
 import android.hardware.input.InputManager;
 import android.os.CombinedVibration;
 import android.os.Handler;
-import android.os.VibrationAttributes;
 import android.os.VibratorManager;
 import android.util.SparseArray;
 import android.view.InputDevice;
@@ -94,11 +93,11 @@
      *
      * @return {@link #isAvailable()}
      */
-    public boolean vibrateIfAvailable(int uid, String opPkg, CombinedVibration effect,
-            String reason, VibrationAttributes attrs) {
+    public boolean vibrateIfAvailable(Vibration.CallerInfo callerInfo, CombinedVibration effect) {
         synchronized (mLock) {
             for (int i = 0; i < mInputDeviceVibrators.size(); i++) {
-                mInputDeviceVibrators.valueAt(i).vibrate(uid, opPkg, effect, reason, attrs);
+                mInputDeviceVibrators.valueAt(i).vibrate(callerInfo.uid, callerInfo.opPkg, effect,
+                        callerInfo.reason, callerInfo.attrs);
             }
             return mInputDeviceVibrators.size() > 0;
         }
diff --git a/services/core/java/com/android/server/vibrator/PerformPrebakedVibratorStep.java b/services/core/java/com/android/server/vibrator/PerformPrebakedVibratorStep.java
index d91bafa..8094e7c5 100644
--- a/services/core/java/com/android/server/vibrator/PerformPrebakedVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/PerformPrebakedVibratorStep.java
@@ -64,7 +64,7 @@
             VibrationEffect fallback = getVibration().getFallback(prebaked.getEffectId());
             long vibratorOnResult = controller.on(prebaked, getVibration().id);
             handleVibratorOnResult(vibratorOnResult);
-            getVibration().stats().reportPerformEffect(vibratorOnResult, prebaked);
+            getVibration().stats.reportPerformEffect(vibratorOnResult, prebaked);
 
             if (vibratorOnResult == 0 && prebaked.shouldFallback()
                     && (fallback instanceof VibrationEffect.Composed)) {
diff --git a/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java b/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java
index 1672470..cce1ef4 100644
--- a/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java
@@ -161,7 +161,7 @@
         }
         long vibratorOnResult = controller.on(duration, getVibration().id);
         handleVibratorOnResult(vibratorOnResult);
-        getVibration().stats().reportVibratorOn(vibratorOnResult);
+        getVibration().stats.reportVibratorOn(vibratorOnResult);
         return vibratorOnResult;
     }
 
diff --git a/services/core/java/com/android/server/vibrator/StartSequentialEffectStep.java b/services/core/java/com/android/server/vibrator/StartSequentialEffectStep.java
index fd1a3ac..15c60a3 100644
--- a/services/core/java/com/android/server/vibrator/StartSequentialEffectStep.java
+++ b/services/core/java/com/android/server/vibrator/StartSequentialEffectStep.java
@@ -93,8 +93,8 @@
             }
 
             mVibratorsOnMaxDuration = startVibrating(effectMapping, nextSteps);
-            conductor.vibratorManagerHooks.noteVibratorOn(conductor.getVibration().uid,
-                    mVibratorsOnMaxDuration);
+            conductor.vibratorManagerHooks.noteVibratorOn(
+                    conductor.getVibration().callerInfo.uid, mVibratorsOnMaxDuration);
         } finally {
             if (mVibratorsOnMaxDuration >= 0) {
                 // It least one vibrator was started then add a finish step to wait for all
@@ -211,7 +211,8 @@
         // Check if sync was prepared and if any step was accepted by a vibrator,
         // otherwise there is nothing to trigger here.
         if (hasPrepared && !hasFailed && maxDuration > 0) {
-            hasTriggered = conductor.vibratorManagerHooks.triggerSyncedVibration(getVibration().id);
+            hasTriggered = conductor.vibratorManagerHooks.triggerSyncedVibration(
+                    getVibration().id);
             hasFailed &= hasTriggered;
         }
 
diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java
index 5667c72..1cc0a4f 100644
--- a/services/core/java/com/android/server/vibrator/Vibration.java
+++ b/services/core/java/com/android/server/vibrator/Vibration.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.CombinedVibration;
+import android.os.IBinder;
 import android.os.VibrationAttributes;
 import android.os.VibrationEffect;
 import android.os.vibrator.PrebakedSegment;
@@ -31,6 +32,7 @@
 import java.text.SimpleDateFormat;
 import java.util.Date;
 import java.util.Objects;
+import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * The base class for all vibrations.
@@ -38,6 +40,13 @@
 class Vibration {
     private static final SimpleDateFormat DEBUG_DATE_FORMAT =
             new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
+    // Used to generate globally unique vibration ids.
+    private static final AtomicInteger sNextVibrationId = new AtomicInteger(1); // 0 = no callback
+
+    public final long id;
+    public final CallerInfo callerInfo;
+    public final VibrationStats stats = new VibrationStats();
+    public final IBinder callerToken;
 
     /** Vibration status with reference to values from vibratormanagerservice.proto for logging. */
     enum Status {
@@ -80,24 +89,82 @@
         }
     }
 
+    Vibration(@NonNull IBinder token, @NonNull CallerInfo callerInfo) {
+        Objects.requireNonNull(token);
+        Objects.requireNonNull(callerInfo);
+        this.id = sNextVibrationId.getAndIncrement();
+        this.callerToken = token;
+        this.callerInfo = callerInfo;
+    }
+
+    /**
+     * Holds lightweight immutable info on the process that triggered the vibration. This data
+     * could potentially be kept in memory for a long time for bugreport dumpsys operations.
+     *
+     * Since CallerInfo can be kept in memory for a long time, it shouldn't hold any references to
+     * potentially expensive or resource-linked objects, such as {@link IBinder}.
+     */
+    static final class CallerInfo {
+        public final VibrationAttributes attrs;
+        public final int uid;
+        public final int displayId;
+        public final String opPkg;
+        public final String reason;
+
+        CallerInfo(@NonNull VibrationAttributes attrs, int uid, int displayId,
+                String opPkg, String reason) {
+            Objects.requireNonNull(attrs);
+            this.attrs = attrs;
+            this.uid = uid;
+            this.displayId = displayId;
+            this.opPkg = opPkg;
+            this.reason = reason;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (!(o instanceof CallerInfo)) return false;
+            CallerInfo that = (CallerInfo) o;
+            return Objects.equals(attrs, that.attrs)
+                    && uid == that.uid
+                    && displayId == that.displayId
+                    && Objects.equals(opPkg, that.opPkg)
+                    && Objects.equals(reason, that.reason);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(attrs, uid, displayId, opPkg, reason);
+        }
+
+        @Override
+        public String toString() {
+            return "CallerInfo{"
+                    + " attrs=" + attrs
+                    + ", uid=" + uid
+                    + ", displayId=" + displayId
+                    + ", opPkg=" + opPkg
+                    + ", reason=" + reason
+                    + '}';
+        }
+    }
+
     /** Immutable info passed as a signal to end a vibration. */
     static final class EndInfo {
         /** The {@link Status} to be set to the vibration when it ends with this info. */
         @NonNull
         public final Status status;
-        /** The UID that triggered the vibration that ended this, or -1 if undefined. */
-        public final int endedByUid;
-        /** The VibrationAttributes.USAGE_* of the vibration that ended this, or -1 if undefined. */
-        public final int endedByUsage;
+        /** Info about the process that ended the vibration. */
+        public final CallerInfo endedBy;
 
         EndInfo(@NonNull Vibration.Status status) {
-            this(status, /* endedByUid= */ -1, /* endedByUsage= */ -1);
+            this(status, null);
         }
 
-        EndInfo(@NonNull Vibration.Status status, int endedByUid, int endedByUsage) {
+        EndInfo(@NonNull Vibration.Status status, @Nullable CallerInfo endedBy) {
             this.status = status;
-            this.endedByUid = endedByUid;
-            this.endedByUsage = endedByUsage;
+            this.endedBy = endedBy;
         }
 
         @Override
@@ -105,27 +172,31 @@
             if (this == o) return true;
             if (!(o instanceof EndInfo)) return false;
             EndInfo that = (EndInfo) o;
-            return endedByUid == that.endedByUid
-                    && endedByUsage == that.endedByUsage
+            return Objects.equals(endedBy, that.endedBy)
                     && status == that.status;
         }
 
         @Override
         public int hashCode() {
-            return Objects.hash(status, endedByUid, endedByUsage);
+            return Objects.hash(status, endedBy);
         }
 
         @Override
         public String toString() {
             return "EndInfo{"
                     + "status=" + status
-                    + ", endedByUid=" + endedByUid
-                    + ", endedByUsage=" + endedByUsage
+                    + ", endedBy=" + endedBy
                     + '}';
         }
     }
 
-    /** Debug information about vibrations. */
+    /**
+     * Holds lightweight debug information about the vibration that could potentially be kept in
+     * memory for a long time for bugreport dumpsys operations.
+     *
+     * Since DebugInfo can be kept in memory for a long time, it shouldn't hold any references to
+     * potentially expensive or resource-linked objects, such as {@link IBinder}.
+     */
     static final class DebugInfo {
         private final long mCreateTime;
         private final long mStartTime;
@@ -134,16 +205,13 @@
         private final CombinedVibration mEffect;
         private final CombinedVibration mOriginalEffect;
         private final float mScale;
-        private final VibrationAttributes mAttrs;
-        private final int mUid;
-        private final int mDisplayId;
-        private final String mOpPkg;
-        private final String mReason;
+        private final CallerInfo mCallerInfo;
         private final Status mStatus;
 
         DebugInfo(Status status, VibrationStats stats, @Nullable CombinedVibration effect,
-                @Nullable CombinedVibration originalEffect, float scale, VibrationAttributes attrs,
-                int uid, int displayId, String opPkg, String reason) {
+                @Nullable CombinedVibration originalEffect, float scale,
+                @NonNull CallerInfo callerInfo) {
+            Objects.requireNonNull(callerInfo);
             mCreateTime = stats.getCreateTimeDebug();
             mStartTime = stats.getStartTimeDebug();
             mEndTime = stats.getEndTimeDebug();
@@ -151,11 +219,7 @@
             mEffect = effect;
             mOriginalEffect = originalEffect;
             mScale = scale;
-            mAttrs = attrs;
-            mUid = uid;
-            mDisplayId = displayId;
-            mOpPkg = opPkg;
-            mReason = reason;
+            mCallerInfo = callerInfo;
             mStatus = status;
         }
 
@@ -179,16 +243,8 @@
                     .append(mOriginalEffect)
                     .append(", scale: ")
                     .append(String.format("%.2f", mScale))
-                    .append(", attrs: ")
-                    .append(mAttrs)
-                    .append(", uid: ")
-                    .append(mUid)
-                    .append(", displayId: ")
-                    .append(mDisplayId)
-                    .append(", opPkg: ")
-                    .append(mOpPkg)
-                    .append(", reason: ")
-                    .append(mReason)
+                    .append(", callerInfo: ")
+                    .append(mCallerInfo)
                     .toString();
         }
 
@@ -201,9 +257,10 @@
             proto.write(VibrationProto.STATUS, mStatus.ordinal());
 
             final long attrsToken = proto.start(VibrationProto.ATTRIBUTES);
-            proto.write(VibrationAttributesProto.USAGE, mAttrs.getUsage());
-            proto.write(VibrationAttributesProto.AUDIO_USAGE, mAttrs.getAudioUsage());
-            proto.write(VibrationAttributesProto.FLAGS, mAttrs.getFlags());
+            final VibrationAttributes attrs = mCallerInfo.attrs;
+            proto.write(VibrationAttributesProto.USAGE, attrs.getUsage());
+            proto.write(VibrationAttributesProto.AUDIO_USAGE, attrs.getAudioUsage());
+            proto.write(VibrationAttributesProto.FLAGS, attrs.getFlags());
             proto.end(attrsToken);
 
             if (mEffect != null) {
diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java
index d944a3b..8a7d607c 100644
--- a/services/core/java/com/android/server/vibrator/VibrationSettings.java
+++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java
@@ -27,6 +27,7 @@
 import static android.os.VibrationAttributes.USAGE_TOUCH;
 import static android.os.VibrationAttributes.USAGE_UNKNOWN;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.IUidObserver;
@@ -375,15 +376,15 @@
      * null otherwise.
      */
     @Nullable
-    public Vibration.Status shouldIgnoreVibration(int uid, int displayId,
-            VibrationAttributes attrs) {
-        final int usage = attrs.getUsage();
+    public Vibration.Status shouldIgnoreVibration(@NonNull Vibration.CallerInfo callerInfo) {
+        final int usage = callerInfo.attrs.getUsage();
         synchronized (mLock) {
-            if (!mUidObserver.isUidForeground(uid)
+            if (!mUidObserver.isUidForeground(callerInfo.uid)
                     && !BACKGROUND_PROCESS_USAGE_ALLOWLIST.contains(usage)) {
                 return Vibration.Status.IGNORED_BACKGROUND;
             }
-            if (mVirtualDeviceListener.isAppOrDisplayOnAnyVirtualDevice(uid, displayId)) {
+            if (mVirtualDeviceListener.isAppOrDisplayOnAnyVirtualDevice(callerInfo.uid,
+                    callerInfo.displayId)) {
                 return Vibration.Status.IGNORED_FROM_VIRTUAL_DEVICE;
             }
 
@@ -391,7 +392,8 @@
                 return Vibration.Status.IGNORED_FOR_POWER;
             }
 
-            if (!attrs.isFlagSet(VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF)) {
+            if (!callerInfo.attrs.isFlagSet(
+                    VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF)) {
                 if (!mVibrateOn && (VIBRATE_ON_DISABLED_USAGE_ALLOWED != usage)) {
                     return Vibration.Status.IGNORED_FOR_SETTINGS;
                 }
@@ -401,7 +403,7 @@
                 }
             }
 
-            if (!attrs.isFlagSet(VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY)) {
+            if (!callerInfo.attrs.isFlagSet(VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY)) {
                 if (!shouldVibrateForRingerModeLocked(usage)) {
                     return Vibration.Status.IGNORED_FOR_RINGER_MODE;
                 }
@@ -420,8 +422,8 @@
      *
      * @return true if the vibration should be cancelled when the screen goes off, false otherwise.
      */
-    public boolean shouldCancelVibrationOnScreenOff(int uid, String opPkg,
-            @VibrationAttributes.Usage int usage, long vibrationStartUptimeMillis) {
+    public boolean shouldCancelVibrationOnScreenOff(@NonNull Vibration.CallerInfo callerInfo,
+            long vibrationStartUptimeMillis) {
         PowerManagerInternal pm;
         synchronized (mLock) {
             pm = mPowerManagerInternal;
@@ -442,12 +444,13 @@
                 return false;
             }
         }
-        if (!SYSTEM_VIBRATION_SCREEN_OFF_USAGE_ALLOWLIST.contains(usage)) {
+        if (!SYSTEM_VIBRATION_SCREEN_OFF_USAGE_ALLOWLIST.contains(callerInfo.attrs.getUsage())) {
             // Usages not allowed even for system vibrations should always be cancelled.
             return true;
         }
         // Only allow vibrations from System packages to continue vibrating when the screen goes off
-        return uid != Process.SYSTEM_UID && uid != 0 && !mSystemUiPackage.equals(opPkg);
+        return callerInfo.uid != Process.SYSTEM_UID && callerInfo.uid != 0
+                && !mSystemUiPackage.equals(callerInfo.opPkg);
     }
 
     /**
diff --git a/services/core/java/com/android/server/vibrator/VibrationStats.java b/services/core/java/com/android/server/vibrator/VibrationStats.java
index 931be1d..2d00351 100644
--- a/services/core/java/com/android/server/vibrator/VibrationStats.java
+++ b/services/core/java/com/android/server/vibrator/VibrationStats.java
@@ -16,6 +16,8 @@
 
 package com.android.server.vibrator;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.os.SystemClock;
 import android.os.vibrator.PrebakedSegment;
 import android.os.vibrator.PrimitiveSegment;
@@ -159,15 +161,18 @@
      * @return true if the status was accepted. This method will only accept given values if
      * the end timestamp was never set.
      */
-    boolean reportEnded(int endedByUid, int endedByUsage) {
+    boolean reportEnded(@Nullable Vibration.CallerInfo endedBy) {
         if (hasEnded()) {
             // Vibration already ended, keep first ending stats set and ignore this one.
             return false;
         }
-        mEndedByUid = endedByUid;
-        mEndedByUsage = endedByUsage;
+        if (endedBy != null) {
+            mEndedByUid = endedBy.uid;
+            mEndedByUsage = endedBy.attrs.getUsage();
+        }
         mEndUptimeMillis = SystemClock.uptimeMillis();
         mEndTimeDebug = System.currentTimeMillis();
+
         return true;
     }
 
@@ -177,9 +182,9 @@
      * <p>This method will only accept the first value as the one that was interrupted by this
      * vibration, and will ignore all successive calls.
      */
-    void reportInterruptedAnotherVibration(int interruptedUsage) {
+    void reportInterruptedAnotherVibration(@NonNull Vibration.CallerInfo callerInfo) {
         if (mInterruptedUsage < 0) {
-            mInterruptedUsage = interruptedUsage;
+            mInterruptedUsage = callerInfo.attrs.getUsage();
         }
     }
 
diff --git a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
index 11cab4b..4202afb 100644
--- a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
+++ b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
@@ -157,7 +157,7 @@
         mNextSteps.offer(new StartSequentialEffectStep(this, sequentialEffect));
         // Vibration will start playing in the Vibrator, following the effect timings and delays.
         // Report current time as the vibration start time, for debugging.
-        mVibration.stats().reportStarted();
+        mVibration.stats.reportStarted();
     }
 
     public HalVibration getVibration() {
diff --git a/services/core/java/com/android/server/vibrator/VibrationThread.java b/services/core/java/com/android/server/vibrator/VibrationThread.java
index d2dc43c..cfb4c74 100644
--- a/services/core/java/com/android/server/vibrator/VibrationThread.java
+++ b/services/core/java/com/android/server/vibrator/VibrationThread.java
@@ -161,7 +161,8 @@
             // for this thread.
             // No point doing this in finally, as if there's an exception, this thread will die
             // and be unusable anyway.
-            mVibratorManagerHooks.onVibrationThreadReleased(mExecutingConductor.getVibration().id);
+            mVibratorManagerHooks.onVibrationThreadReleased(
+                    mExecutingConductor.getVibration().id);
             synchronized (mLock) {
                 mLock.notifyAll();
             }
@@ -230,7 +231,8 @@
 
     /** Runs the VibrationThread ensuring that the wake lock is acquired and released. */
     private void runCurrentVibrationWithWakeLock() {
-        WorkSource workSource = new WorkSource(mExecutingConductor.getVibration().uid);
+        WorkSource workSource = new WorkSource(
+                mExecutingConductor.getVibration().callerInfo.uid);
         mWakeLock.setWorkSource(workSource);
         mWakeLock.acquire();
         try {
@@ -251,7 +253,7 @@
      * Called from within runWithWakeLock.
      */
     private void runCurrentVibrationWithWakeLockAndDeathLink() {
-        IBinder vibrationBinderToken = mExecutingConductor.getVibration().token;
+        IBinder vibrationBinderToken = mExecutingConductor.getVibration().callerToken;
         try {
             vibrationBinderToken.linkToDeath(mExecutingConductor, 0);
         } catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index c87332d..5756414 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -77,7 +77,6 @@
 import java.util.Arrays;
 import java.util.LinkedList;
 import java.util.List;
-import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Consumer;
 import java.util.function.Function;
 
@@ -123,9 +122,6 @@
         }
     }
 
-    // Used to generate globally unique vibration ids.
-    private final AtomicInteger mNextVibrationId = new AtomicInteger(1); // 0 = no callback
-
     private final Object mLock = new Object();
     private final Context mContext;
     private final PowerManager.WakeLock mWakeLock;
@@ -367,8 +363,9 @@
                     // missing on individual vibrators.
                     return false;
                 }
-                AlwaysOnVibration alwaysOnVibration = new AlwaysOnVibration(
-                        alwaysOnId, uid, opPkg, attrs, effects);
+                AlwaysOnVibration alwaysOnVibration = new AlwaysOnVibration(alwaysOnId,
+                        new Vibration.CallerInfo(attrs, uid, Display.DEFAULT_DISPLAY, opPkg,
+                                null), effects);
                 mAlwaysOnEffects.put(alwaysOnId, alwaysOnVibration);
                 updateAlwaysOnLocked(alwaysOnVibration);
             }
@@ -408,8 +405,8 @@
             }
             attrs = fixupVibrationAttributes(attrs, effect);
             // Create Vibration.Stats as close to the received request as possible, for tracking.
-            HalVibration vib = new HalVibration(token, mNextVibrationId.getAndIncrement(), effect,
-                    attrs, uid, displayId, opPkg, reason);
+            HalVibration vib = new HalVibration(token, effect,
+                    new Vibration.CallerInfo(attrs, uid, displayId, opPkg, reason));
             fillVibrationFallbacks(vib, effect);
 
             if (attrs.isFlagSet(VibrationAttributes.FLAG_INVALIDATE_SETTINGS_CACHE)) {
@@ -422,27 +419,23 @@
                 if (DEBUG) {
                     Slog.d(TAG, "Starting vibrate for vibration " + vib.id);
                 }
-                int ignoredByUid = -1;
-                int ignoredByUsage = -1;
-                Vibration.Status status = null;
+                Vibration.CallerInfo ignoredByCallerInfo = null;
+                Vibration.Status status;
 
                 // Check if user settings or DnD is set to ignore this vibration.
-                status = shouldIgnoreVibrationLocked(vib.uid, vib.displayId, vib.opPkg, vib.attrs);
+                status = shouldIgnoreVibrationLocked(vib.callerInfo);
 
                 // Check if something has external control, assume it's more important.
                 if ((status == null) && (mCurrentExternalVibration != null)) {
                     status = Vibration.Status.IGNORED_FOR_EXTERNAL;
-                    ignoredByUid = mCurrentExternalVibration.externalVibration.getUid();
-                    ignoredByUsage = mCurrentExternalVibration.externalVibration
-                            .getVibrationAttributes().getUsage();
+                    ignoredByCallerInfo = mCurrentExternalVibration.callerInfo;
                 }
 
                 // Check if ongoing vibration is more important than this vibration.
                 if (status == null) {
                     status = shouldIgnoreVibrationForOngoingLocked(vib);
                     if (status != null) {
-                        ignoredByUid = mCurrentVibration.getVibration().uid;
-                        ignoredByUsage = mCurrentVibration.getVibration().attrs.getUsage();
+                        ignoredByCallerInfo = mCurrentVibration.getVibration().callerInfo;
                     }
                 }
 
@@ -460,12 +453,11 @@
                                     Slog.d(TAG, "Pipelining vibration " + vib.id);
                                 }
                             } else {
-                                vib.stats().reportInterruptedAnotherVibration(
-                                        mCurrentVibration.getVibration().attrs.getUsage());
+                                vib.stats.reportInterruptedAnotherVibration(
+                                        mCurrentVibration.getVibration().callerInfo);
                                 mCurrentVibration.notifyCancelled(
-                                        new Vibration.EndInfo(
-                                                Vibration.Status.CANCELLED_SUPERSEDED, vib.uid,
-                                                vib.attrs.getUsage()),
+                                        new Vibration.EndInfo(Vibration.Status.CANCELLED_SUPERSEDED,
+                                                vib.callerInfo),
                                         /* immediate= */ false);
                             }
                         }
@@ -478,7 +470,7 @@
                 // Ignored or failed to start the vibration, end it and report metrics right away.
                 if (status != Vibration.Status.RUNNING) {
                     endVibrationLocked(vib,
-                            new Vibration.EndInfo(status, ignoredByUid, ignoredByUsage),
+                            new Vibration.EndInfo(status, ignoredByCallerInfo),
                             /* shouldWriteStats= */ true);
                 }
                 return vib;
@@ -641,8 +633,7 @@
             }
 
             HalVibration vib = mCurrentVibration.getVibration();
-            Vibration.Status ignoreStatus = shouldIgnoreVibrationLocked(
-                    vib.uid, vib.displayId, vib.opPkg, vib.attrs);
+            Vibration.Status ignoreStatus = shouldIgnoreVibrationLocked(vib.callerInfo);
 
             if (inputDevicesChanged || (ignoreStatus != null)) {
                 if (DEBUG) {
@@ -671,10 +662,9 @@
             if (vibrator == null) {
                 continue;
             }
-            Vibration.Status ignoreStatus = shouldIgnoreVibrationLocked(
-                    vib.uid, Display.DEFAULT_DISPLAY, vib.opPkg, vib.attrs);
+            Vibration.Status ignoreStatus = shouldIgnoreVibrationLocked(vib.callerInfo);
             if (ignoreStatus == null) {
-                effect = mVibrationScaler.scale(effect, vib.attrs.getUsage());
+                effect = mVibrationScaler.scale(effect, vib.callerInfo.attrs.getUsage());
             } else {
                 // Vibration should not run, use null effect to remove registered effect.
                 effect = null;
@@ -687,9 +677,10 @@
     private Vibration.Status startVibrationLocked(HalVibration vib) {
         Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationLocked");
         try {
-            vib.updateEffects(effect -> mVibrationScaler.scale(effect, vib.attrs.getUsage()));
+            vib.updateEffects(
+                    effect -> mVibrationScaler.scale(effect, vib.callerInfo.attrs.getUsage()));
             boolean inputDevicesAvailable = mInputDeviceDelegate.vibrateIfAvailable(
-                    vib.uid, vib.opPkg, vib.getEffect(), vib.reason, vib.attrs);
+                    vib.callerInfo, vib.getEffect());
             if (inputDevicesAvailable) {
                 return Vibration.Status.FORWARDED_TO_INPUT_DEVICES;
             }
@@ -704,8 +695,7 @@
             // Note that we don't consider pipelining here, because new pipelined ones should
             // replace pending non-executing pipelined ones anyway.
             clearNextVibrationLocked(
-                    new Vibration.EndInfo(Vibration.Status.IGNORED_SUPERSEDED,
-                            vib.uid, vib.attrs.getUsage()));
+                    new Vibration.EndInfo(Vibration.Status.IGNORED_SUPERSEDED, vib.callerInfo));
             mNextVibration = conductor;
             return Vibration.Status.RUNNING;
         } finally {
@@ -718,7 +708,7 @@
         Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationThreadLocked");
         try {
             HalVibration vib = conductor.getVibration();
-            int mode = startAppOpModeLocked(vib.uid, vib.opPkg, vib.attrs);
+            int mode = startAppOpModeLocked(vib.callerInfo);
             switch (mode) {
                 case AppOpsManager.MODE_ALLOWED:
                     Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
@@ -731,7 +721,8 @@
                     }
                     return Vibration.Status.RUNNING;
                 case AppOpsManager.MODE_ERRORED:
-                    Slog.w(TAG, "Start AppOpsManager operation errored for uid " + vib.uid);
+                    Slog.w(TAG, "Start AppOpsManager operation errored for uid "
+                            + vib.callerInfo.uid);
                     return Vibration.Status.IGNORED_ERROR_APP_OPS;
                 default:
                     return Vibration.Status.IGNORED_APP_OPS;
@@ -745,7 +736,8 @@
     private void endVibrationLocked(HalVibration vib, Vibration.EndInfo vibrationEndInfo,
             boolean shouldWriteStats) {
         vib.end(vibrationEndInfo);
-        logVibrationStatus(vib.uid, vib.attrs, vibrationEndInfo.status);
+        logVibrationStatus(vib.callerInfo.uid, vib.callerInfo.attrs,
+                vibrationEndInfo.status);
         mVibratorManagerRecords.record(vib);
         if (shouldWriteStats) {
             mFrameworkStatsLogger.writeVibrationReportedAsync(
@@ -817,12 +809,13 @@
         try {
             HalVibration vib = mCurrentVibration.getVibration();
             if (DEBUG) {
-                Slog.d(TAG, "Reporting vibration " + vib.id + " finished with " + vibrationEndInfo);
+                Slog.d(TAG, "Reporting vibration " + vib.id + " finished with "
+                        + vibrationEndInfo);
             }
             // DO NOT write metrics at this point, wait for the VibrationThread to report the
             // vibration was released, after all cleanup. The metrics will be reported then.
             endVibrationLocked(vib, vibrationEndInfo, /* shouldWriteStats= */ false);
-            finishAppOpModeLocked(vib.uid, vib.opPkg);
+            finishAppOpModeLocked(vib.callerInfo);
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
         }
@@ -830,7 +823,8 @@
 
     private void onSyncedVibrationComplete(long vibrationId) {
         synchronized (mLock) {
-            if (mCurrentVibration != null && mCurrentVibration.getVibration().id == vibrationId) {
+            if (mCurrentVibration != null
+                    && mCurrentVibration.getVibration().id == vibrationId) {
                 if (DEBUG) {
                     Slog.d(TAG, "Synced vibration " + vibrationId + " complete, notifying thread");
                 }
@@ -841,7 +835,8 @@
 
     private void onVibrationComplete(int vibratorId, long vibrationId) {
         synchronized (mLock) {
-            if (mCurrentVibration != null && mCurrentVibration.getVibration().id == vibrationId) {
+            if (mCurrentVibration != null
+                    && mCurrentVibration.getVibration().id == vibrationId) {
                 if (DEBUG) {
                     Slog.d(TAG, "Vibration " + vibrationId + " on vibrator " + vibratorId
                             + " complete, notifying thread");
@@ -871,8 +866,8 @@
             return null;
         }
 
-        int currentUsage = currentVibration.attrs.getUsage();
-        int newUsage = vib.attrs.getUsage();
+        int currentUsage = currentVibration.callerInfo.attrs.getUsage();
+        int newUsage = vib.callerInfo.attrs.getUsage();
         if (getVibrationImportance(currentUsage) > getVibrationImportance(newUsage)) {
             // Current vibration has higher importance than this one and should not be cancelled.
             return Vibration.Status.IGNORED_FOR_HIGHER_IMPORTANCE;
@@ -916,15 +911,13 @@
      */
     @GuardedBy("mLock")
     @Nullable
-    private Vibration.Status shouldIgnoreVibrationLocked(int uid, int displayId, String opPkg,
-            VibrationAttributes attrs) {
-        Vibration.Status statusFromSettings = mVibrationSettings.shouldIgnoreVibration(uid,
-                displayId, attrs);
+    private Vibration.Status shouldIgnoreVibrationLocked(Vibration.CallerInfo callerInfo) {
+        Vibration.Status statusFromSettings = mVibrationSettings.shouldIgnoreVibration(callerInfo);
         if (statusFromSettings != null) {
             return statusFromSettings;
         }
 
-        int mode = checkAppOpModeLocked(uid, opPkg, attrs);
+        int mode = checkAppOpModeLocked(callerInfo);
         if (mode != AppOpsManager.MODE_ALLOWED) {
             if (mode == AppOpsManager.MODE_ERRORED) {
                 // We might be getting calls from within system_server, so we don't actually
@@ -948,7 +941,8 @@
      *                    started with the same token can be cancelled with it.
      */
     private boolean shouldCancelVibration(HalVibration vib, int usageFilter, IBinder token) {
-        return (vib.token == token) && shouldCancelVibration(vib.attrs, usageFilter);
+        return (vib.callerToken == token) && shouldCancelVibration(vib.callerInfo.attrs,
+                usageFilter);
     }
 
     /**
@@ -973,24 +967,25 @@
      * {@code attrs}. This will return one of the AppOpsManager.MODE_*.
      */
     @GuardedBy("mLock")
-    private int checkAppOpModeLocked(int uid, String opPkg, VibrationAttributes attrs) {
+    private int checkAppOpModeLocked(Vibration.CallerInfo callerInfo) {
         int mode = mAppOps.checkAudioOpNoThrow(AppOpsManager.OP_VIBRATE,
-                attrs.getAudioUsage(), uid, opPkg);
-        int fixedMode = fixupAppOpModeLocked(mode, attrs);
+                callerInfo.attrs.getAudioUsage(), callerInfo.uid, callerInfo.opPkg);
+        int fixedMode = fixupAppOpModeLocked(mode, callerInfo.attrs);
         if (mode != fixedMode && fixedMode == AppOpsManager.MODE_ALLOWED) {
             // If we're just ignoring the vibration op then this is set by DND and we should ignore
             // if we're asked to bypass. AppOps won't be able to record this operation, so make
             // sure we at least note it in the logs for debugging.
-            Slog.d(TAG, "Bypassing DND for vibrate from uid " + uid);
+            Slog.d(TAG, "Bypassing DND for vibrate from uid " + callerInfo.uid);
         }
         return fixedMode;
     }
 
     /** Start an operation in {@link AppOpsManager}, if allowed. */
     @GuardedBy("mLock")
-    private int startAppOpModeLocked(int uid, String opPkg, VibrationAttributes attrs) {
+    private int startAppOpModeLocked(Vibration.CallerInfo callerInfo) {
         return fixupAppOpModeLocked(
-                mAppOps.startOpNoThrow(AppOpsManager.OP_VIBRATE, uid, opPkg), attrs);
+                mAppOps.startOpNoThrow(AppOpsManager.OP_VIBRATE, callerInfo.uid, callerInfo.opPkg),
+                callerInfo.attrs);
     }
 
     /**
@@ -998,8 +993,8 @@
      * operation with same uid was previously started.
      */
     @GuardedBy("mLock")
-    private void finishAppOpModeLocked(int uid, String opPkg) {
-        mAppOps.finishOp(AppOpsManager.OP_VIBRATE, uid, opPkg);
+    private void finishAppOpModeLocked(Vibration.CallerInfo callerInfo) {
+        mAppOps.finishOp(AppOpsManager.OP_VIBRATE, callerInfo.uid, callerInfo.opPkg);
     }
 
     /**
@@ -1186,8 +1181,8 @@
             return false;
         }
         HalVibration vib = conductor.getVibration();
-        return mVibrationSettings.shouldCancelVibrationOnScreenOff(
-                vib.uid, vib.opPkg, vib.attrs.getUsage(), vib.stats().getCreateUptimeMillis());
+        return mVibrationSettings.shouldCancelVibrationOnScreenOff(vib.callerInfo,
+                vib.stats.getCreateUptimeMillis());
     }
 
     @GuardedBy("mLock")
@@ -1385,31 +1380,33 @@
      */
     private static final class AlwaysOnVibration {
         public final int alwaysOnId;
-        public final int uid;
-        public final String opPkg;
-        public final VibrationAttributes attrs;
+        public final Vibration.CallerInfo callerInfo;
         public final SparseArray<PrebakedSegment> effects;
 
-        AlwaysOnVibration(int alwaysOnId, int uid, String opPkg, VibrationAttributes attrs,
+        AlwaysOnVibration(int alwaysOnId, Vibration.CallerInfo callerInfo,
                 SparseArray<PrebakedSegment> effects) {
             this.alwaysOnId = alwaysOnId;
-            this.uid = uid;
-            this.opPkg = opPkg;
-            this.attrs = attrs;
+            this.callerInfo = callerInfo;
             this.effects = effects;
         }
     }
 
     /** Holder for a {@link ExternalVibration}. */
-    private final class ExternalVibrationHolder implements IBinder.DeathRecipient {
+    private final class ExternalVibrationHolder extends Vibration implements
+            IBinder.DeathRecipient {
 
         public final ExternalVibration externalVibration;
-        public final VibrationStats stats = new VibrationStats();
         public int scale;
 
         private Vibration.Status mStatus;
 
         private ExternalVibrationHolder(ExternalVibration externalVibration) {
+            super(externalVibration.getToken(), new Vibration.CallerInfo(
+                    externalVibration.getVibrationAttributes(), externalVibration.getUid(),
+                    // TODO(b/243604888): propagating displayID from IExternalVibration instead of
+                    //  using INVALID_DISPLAY for all external vibrations.
+                    Display.INVALID_DISPLAY,
+                    externalVibration.getPackage(), null));
             this.externalVibration = externalVibration;
             this.scale = IExternalVibratorService.SCALE_NONE;
             mStatus = Vibration.Status.RUNNING;
@@ -1437,14 +1434,15 @@
                 return;
             }
             mStatus = info.status;
-            stats.reportEnded(info.endedByUid, info.endedByUsage);
+            stats.reportEnded(info.endedBy);
 
             if (stats.hasStarted()) {
                 // External vibration doesn't have feedback from total time the vibrator was playing
                 // with non-zero amplitude, so we use the duration between start and end times of
                 // the vibration as the time the vibrator was ON, since the haptic channels are
                 // open for this duration and can receive vibration waveform data.
-                stats.reportVibratorOn(stats.getEndUptimeMillis() - stats.getStartUptimeMillis());
+                stats.reportVibratorOn(
+                        stats.getEndUptimeMillis() - stats.getStartUptimeMillis());
             }
         }
 
@@ -1462,13 +1460,8 @@
         }
 
         public Vibration.DebugInfo getDebugInfo() {
-            return new Vibration.DebugInfo(
-                    mStatus, stats, /* effect= */ null, /* originalEffect= */ null, scale,
-                    externalVibration.getVibrationAttributes(), externalVibration.getUid(),
-                    // TODO(b/243604888): propagating displayID from IExternalVibration instead of
-                    // using INVALID_DISPLAY for all external vibrations.
-                    Display.INVALID_DISPLAY,
-                    externalVibration.getPackage(), /* reason= */ null);
+            return new Vibration.DebugInfo(mStatus, stats, /* effect= */ null,
+                    /* originalEffect= */ null, scale, callerInfo);
         }
 
         public VibrationStats.StatsInfo getStatsInfo(long completionUptimeMillis) {
@@ -1538,7 +1531,7 @@
         }
 
         synchronized void record(HalVibration vib) {
-            int usage = vib.attrs.getUsage();
+            int usage = vib.callerInfo.attrs.getUsage();
             if (!mPreviousVibrations.contains(usage)) {
                 mPreviousVibrations.put(usage, new LinkedList<>());
             }
@@ -1679,8 +1672,7 @@
             synchronized (mLock) {
                 // TODO(b/243604888): propagating displayID from IExternalVibration instead of
                 // using INVALID_DISPLAY for all external vibrations.
-                Vibration.Status ignoreStatus = shouldIgnoreVibrationLocked(
-                        vib.getUid(), Display.INVALID_DISPLAY, vib.getPackage(), attrs);
+                Vibration.Status ignoreStatus = shouldIgnoreVibrationLocked(vibHolder.callerInfo);
                 if (ignoreStatus != null) {
                     vibHolder.scale = IExternalVibratorService.SCALE_MUTE;
                     // Failed to start the vibration, end it and report metrics right away.
@@ -1698,13 +1690,13 @@
                     // vibration that may be playing and ready the vibrator for external control.
                     if (mCurrentVibration != null) {
                         vibHolder.stats.reportInterruptedAnotherVibration(
-                                mCurrentVibration.getVibration().attrs.getUsage());
+                                mCurrentVibration.getVibration().callerInfo);
                         clearNextVibrationLocked(
                                 new Vibration.EndInfo(Vibration.Status.IGNORED_FOR_EXTERNAL,
-                                        vib.getUid(), attrs.getUsage()));
+                                        vibHolder.callerInfo));
                         mCurrentVibration.notifyCancelled(
                                 new Vibration.EndInfo(Vibration.Status.CANCELLED_SUPERSEDED,
-                                        vib.getUid(), attrs.getUsage()),
+                                        vibHolder.callerInfo),
                                 /* immediate= */ true);
                         waitForCompletion = true;
                     }
@@ -1720,11 +1712,10 @@
                     alreadyUnderExternalControl = true;
                     mCurrentExternalVibration.mute();
                     vibHolder.stats.reportInterruptedAnotherVibration(
-                            mCurrentExternalVibration.externalVibration
-                                    .getVibrationAttributes().getUsage());
+                            mCurrentExternalVibration.callerInfo);
                     endExternalVibrateLocked(
                             new Vibration.EndInfo(Vibration.Status.CANCELLED_SUPERSEDED,
-                                    vib.getUid(), attrs.getUsage()),
+                                    vibHolder.callerInfo),
                             /* continueExternalControl= */ true);
                 }
                 mCurrentExternalVibration = vibHolder;
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 198c339..477a8a6 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -3154,8 +3154,9 @@
                 }
             }
 
-            final ActivityOptions clientOptions = ActivityOptions.makeBasic();
-            clientOptions.setIgnorePendingIntentCreatorForegroundState(true);
+            final ActivityOptions clientOptions = ActivityOptions.makeBasic()
+                    .setPendingIntentCreatorBackgroundActivityStartMode(
+                            ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED);
             PendingIntent clientIntent = PendingIntent.getActivityAsUser(
                     mContext, 0, Intent.createChooser(
                             new Intent(Intent.ACTION_SET_WALLPAPER),
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index 4507637..502bfd1 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -781,6 +781,7 @@
                 // the running transition finish.
                 final Transition transition = r != null
                         && r.mTransitionController.inPlayingTransition(r)
+                        && !r.mTransitionController.isCollecting()
                         ? r.mTransitionController.createTransition(TRANSIT_TO_BACK) : null;
                 if (transition != null) {
                     r.mTransitionController.requestStartTransition(transition, null /*startTask */,
@@ -820,6 +821,7 @@
                 // visibility while playing transition, there won't able to commit visibility until
                 // the running transition finish.
                 final Transition transition = r.mTransitionController.inPlayingTransition(r)
+                        && !r.mTransitionController.isCollecting()
                         ? r.mTransitionController.createTransition(TRANSIT_TO_FRONT) : null;
                 if (transition != null) {
                     r.mTransitionController.requestStartTransition(transition, null /*startTask */,
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index c289153..81ee9cd 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -3919,6 +3919,10 @@
         }
     }
 
+    boolean isFinishing() {
+        return finishing;
+    }
+
     /**
      * This method is to only be called from the client via binder when the activity is destroyed
      * AND finished.
@@ -5111,6 +5115,11 @@
         return mDeferHidingClient;
     }
 
+    boolean canAffectSystemUiFlags() {
+        return task != null && task.canAffectSystemUiFlags() && isVisible()
+                && !inPinnedWindowingMode();
+    }
+
     @Override
     boolean isVisible() {
         // If the activity isn't hidden then it is considered visible and there is no need to check
@@ -8375,7 +8384,13 @@
     }
 
     void recomputeConfiguration() {
-        onRequestedOverrideConfigurationChanged(getRequestedOverrideConfiguration());
+        // We check if the current activity is transparent. In that case we need to
+        // recomputeConfiguration of the first opaque activity beneath, to allow a
+        // proper computation of the new bounds.
+        if (!mLetterboxUiController.applyOnOpaqueActivityBelow(
+                ActivityRecord::recomputeConfiguration)) {
+            onRequestedOverrideConfigurationChanged(getRequestedOverrideConfiguration());
+        }
     }
 
     boolean isInTransition() {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index ca3cfaf..719edee 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -3140,22 +3140,6 @@
     }
 
     /**
-     * Returns true if the input point is within an app window.
-     */
-    boolean pointWithinAppWindow(int x, int y) {
-        final int[] targetWindowType = {-1};
-        forAllWindows(w -> {
-            if (w.isOnScreen() && w.isVisible() && w.getFrame().contains(x, y)) {
-                targetWindowType[0] = w.mAttrs.type;
-                return true;
-            }
-            return false;
-        }, true /* traverseTopToBottom */);
-        return FIRST_APPLICATION_WINDOW <= targetWindowType[0]
-                && targetWindowType[0] <= LAST_APPLICATION_WINDOW;
-    }
-
-    /**
      * Find the task whose outside touch area (for resizing) (x, y) falls within.
      * Returns null if the touch doesn't fall into a resizing area.
      */
diff --git a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
index e04900c..47bdba3 100644
--- a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
@@ -250,10 +250,7 @@
         }
         ActivityRecord topActivity = mDisplayContent.topRunningActivity(
                     /* considerKeyguardState= */ true);
-        if (topActivity == null
-                // Checking windowing mode on activity level because we don't want to
-                // show toast in case of activity embedding.
-                || topActivity.getWindowingMode() != WINDOWING_MODE_FULLSCREEN) {
+        if (!isTreatmentEnabledForActivity(topActivity)) {
             return;
         }
         showToast(R.string.display_rotation_camera_compat_toast_after_rotation);
@@ -309,21 +306,28 @@
     }
 
     boolean isActivityEligibleForOrientationOverride(@NonNull ActivityRecord activity) {
-        return isTreatmentEnabledForDisplay() && isCameraActiveInFullscreen(activity);
+        return isTreatmentEnabledForDisplay()
+                && isCameraActive(activity, /* mustBeFullscreen */ true);
     }
 
+
     /**
      * Whether camera compat treatment is applicable for the given activity.
      *
      * <p>Conditions that need to be met:
      * <ul>
-     *     <li>{@link #isCameraActiveForPackage} is {@code true} for the activity.
+     *     <li>Camera is active for the package.
      *     <li>The activity is in fullscreen
      *     <li>The activity has fixed orientation but not "locked" or "nosensor" one.
      * </ul>
      */
     boolean isTreatmentEnabledForActivity(@Nullable ActivityRecord activity) {
-        return activity != null && isCameraActiveInFullscreen(activity)
+        return isTreatmentEnabledForActivity(activity, /* mustBeFullscreen */ true);
+    }
+
+    private boolean isTreatmentEnabledForActivity(@Nullable ActivityRecord activity,
+            boolean mustBeFullscreen) {
+        return activity != null && isCameraActive(activity, mustBeFullscreen)
                 && activity.getRequestedConfigurationOrientation() != ORIENTATION_UNDEFINED
                 // "locked" and "nosensor" values are often used by camera apps that can't
                 // handle dynamic changes so we shouldn't force rotate them.
@@ -331,8 +335,10 @@
                 && activity.getOverrideOrientation() != SCREEN_ORIENTATION_LOCKED;
     }
 
-    private boolean isCameraActiveInFullscreen(@NonNull ActivityRecord activity) {
-        return !activity.inMultiWindowMode()
+    private boolean isCameraActive(@NonNull ActivityRecord activity, boolean mustBeFullscreen) {
+        // Checking windowing mode on activity level because we don't want to
+        // apply treatment in case of activity embedding.
+        return (!mustBeFullscreen || !activity.inMultiWindowMode())
                 && mCameraIdPackageBiMap.containsPackageName(activity.packageName)
                 && activity.mLetterboxUiController.shouldForceRotateForCameraCompat();
     }
@@ -385,7 +391,8 @@
         }
         // Checking that the whole app is in multi-window mode as we shouldn't show toast
         // for the activity embedding case.
-        if (topActivity.getTask().getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW) {
+        if (topActivity.getTask().getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW
+                && isTreatmentEnabledForActivity(topActivity, /* mustBeFullscreen */ false)) {
             showToast(R.string.display_rotation_camera_compat_toast_in_split_screen);
         }
     }
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 9681789..d2e8ad1 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -81,6 +81,7 @@
 import static java.lang.Boolean.FALSE;
 import static java.lang.Boolean.TRUE;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager.TaskDescription;
 import android.content.pm.ActivityInfo.ScreenOrientation;
@@ -104,7 +105,10 @@
 import com.android.server.wm.LetterboxConfiguration.LetterboxBackgroundType;
 
 import java.io.PrintWriter;
+import java.util.Optional;
 import java.util.function.BooleanSupplier;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
 
 /** Controls behaviour of the letterbox UI for {@link mActivityRecord}. */
 // TODO(b/185262487): Improve test coverage of this class. Parts of it are tested in
@@ -114,6 +118,9 @@
 // TODO(b/263021211): Consider renaming to more generic CompatUIController.
 final class LetterboxUiController {
 
+    private static final Predicate<ActivityRecord> FIRST_OPAQUE_NOT_FINISHING_ACTIVITY_PREDICATE =
+            activityRecord -> activityRecord.fillsParent() && !activityRecord.isFinishing();
+
     private static final String TAG = TAG_WITH_CLASS_NAME ? "LetterboxUiController" : TAG_ATM;
 
     private static final float UNDEFINED_ASPECT_RATIO = 0f;
@@ -1390,7 +1397,8 @@
             return;
         }
         final ActivityRecord firstOpaqueActivityBeneath = mActivityRecord.getTask().getActivity(
-                ActivityRecord::fillsParent, mActivityRecord, false /* includeBoundary */,
+                FIRST_OPAQUE_NOT_FINISHING_ACTIVITY_PREDICATE /* callback */,
+                mActivityRecord /* boundary */, false /* includeBoundary */,
                 true /* traverseTopToBottom */);
         if (firstOpaqueActivityBeneath == null) {
             // We skip letterboxing if the translucent activity doesn't have any opaque
@@ -1466,6 +1474,32 @@
         return mInheritedCompatDisplayInsets;
     }
 
+    /**
+     * In case of translucent activities, it consumes the {@link ActivityRecord} of the first opaque
+     * activity beneath using the given consumer and returns {@code true}.
+     */
+    boolean applyOnOpaqueActivityBelow(@NonNull Consumer<ActivityRecord> consumer) {
+        return findOpaqueNotFinishingActivityBelow()
+                .map(activityRecord -> {
+                    consumer.accept(activityRecord);
+                    return true;
+                }).orElse(false);
+    }
+
+    /**
+     * @return The first not finishing opaque activity beneath the current translucent activity
+     * if it exists and the strategy is enabled.
+     */
+    private Optional<ActivityRecord> findOpaqueNotFinishingActivityBelow() {
+        if (!hasInheritedLetterboxBehavior() || mActivityRecord.getTask() == null) {
+            return Optional.empty();
+        }
+        return Optional.ofNullable(mActivityRecord.getTask().getActivity(
+                FIRST_OPAQUE_NOT_FINISHING_ACTIVITY_PREDICATE /* callback */,
+                mActivityRecord /* boundary */, false /* includeBoundary */,
+                true /* traverseTopToBottom */));
+    }
+
     private void inheritConfiguration(ActivityRecord firstOpaque) {
         // To avoid wrong behaviour, we're not forcing a specific aspet ratio to activities
         // which are not already providing one (e.g. permission dialogs) and presumably also
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index 4be1c83..14b845c 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -33,6 +33,8 @@
 import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.os.Process.SYSTEM_UID;
+import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
+import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TASKS;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RECENTS;
@@ -215,10 +217,16 @@
             int y = (int) ev.getY();
             mService.mH.post(PooledLambda.obtainRunnable((nonArg) -> {
                 synchronized (mService.mGlobalLock) {
-                    // Unfreeze the task list once we touch down in a task
                     final RootWindowContainer rac = mService.mRootWindowContainer;
                     final DisplayContent dc = rac.getDisplayContent(displayId).mDisplayContent;
-                    if (dc.pointWithinAppWindow(x, y)) {
+                    final WindowState win = dc.getTouchableWinAtPointLocked((float) x, (float) y);
+                    if (win == null) {
+                        return;
+                    }
+                    // Unfreeze the task list once we touch down in a task
+                    final boolean isAppWindowTouch = FIRST_APPLICATION_WINDOW <= win.mAttrs.type
+                            && win.mAttrs.type <= LAST_APPLICATION_WINDOW;
+                    if (isAppWindowTouch) {
                         final Task stack = mService.getTopDisplayFocusedRootTask();
                         final Task topTask = stack != null ? stack.getTopMostTask() : null;
                         resetFreezeTaskListReordering(topTask);
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index b28e807..d212aee 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -2012,17 +2012,15 @@
         return mainWin.getAttrs().rotationAnimation;
     }
 
-    /** Applies the new configuration and returns {@code true} if there is a display change. */
-    boolean applyDisplayChangeIfNeeded() {
-        boolean changed = false;
+    /** Applies the new configuration for the changed displays. */
+    void applyDisplayChangeIfNeeded() {
         for (int i = mParticipants.size() - 1; i >= 0; --i) {
             final WindowContainer<?> wc = mParticipants.valueAt(i);
             final DisplayContent dc = wc.asDisplayContent();
             if (dc == null || !mChanges.get(dc).hasChanged()) continue;
             dc.sendNewConfiguration();
-            changed = true;
+            setReady(dc, true);
         }
-        return changed;
     }
 
     boolean getLegacyIsReady() {
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index 6b2bf59..8708f73 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -41,6 +41,8 @@
 
     private static final String TAG = TAG_WITH_CLASS_NAME ? "WallpaperWindowToken" : TAG_WM;
 
+    private boolean mShowWhenLocked = false;
+
     WallpaperWindowToken(WindowManagerService service, IBinder token, boolean explicit,
             DisplayContent dc, boolean ownerCanManageAppTokens) {
         this(service, token, explicit, dc, ownerCanManageAppTokens, null /* options */);
@@ -65,6 +67,29 @@
         mDisplayContent.mWallpaperController.removeWallpaperToken(this);
     }
 
+    /**
+     * Controls whether this wallpaper shows underneath the keyguard or is hidden and only
+     * revealed once keyguard is dismissed.
+     */
+    void setShowWhenLocked(boolean showWhenLocked) {
+        if (showWhenLocked == mShowWhenLocked) {
+            return;
+        }
+        mShowWhenLocked = showWhenLocked;
+
+        // Move the window token to the front (private) or back (showWhenLocked). This is possible
+        // because the DisplayArea underneath TaskDisplayArea only contains TYPE_WALLPAPER windows.
+        final int position = showWhenLocked ? POSITION_BOTTOM : POSITION_TOP;
+
+        // Note: Moving all the way to the front or back breaks ordering based on addition times.
+        // We should never have more than one non-animating token of each type.
+        getParent().positionChildAt(position, this /* child */, false  /*includingParents */);
+    }
+
+    boolean canShowWhenLocked() {
+        return mShowWhenLocked;
+    }
+
     void sendWindowWallpaperCommand(
             String action, int x, int y, int z, Bundle extras, boolean sync) {
         for (int wallpaperNdx = mChildren.size() - 1; wallpaperNdx >= 0; wallpaperNdx--) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 1282acb..2f3a70e 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -657,6 +657,17 @@
     public abstract int getWindowOwnerUserId(IBinder windowToken);
 
     /**
+     * Control visilibility of a {@link WallpaperWindowToken} {@code} binder on the lock screen.
+     *
+     * <p>This will also affect its Z-ordering as {@code showWhenLocked} wallpaper tokens are
+     * arranged underneath non-{@code showWhenLocked} wallpaper tokens.
+     *
+     * @param windowToken wallpaper token previously added via {@link #addWindowToken}
+     * @param showWhenLocked whether {@param token} can continue to be shown on the lock screen.
+     */
+    public abstract void setWallpaperShowWhenLocked(IBinder windowToken, boolean showWhenLocked);
+
+    /**
      * Returns {@code true} if a Window owned by {@code uid} has focus.
      */
     public abstract boolean isUidFocused(int uid);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 5f49aeb..22fddec 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -3844,8 +3844,9 @@
      *
      * If {@code com.android.internal.R.bool.config_perDisplayFocusEnabled} is set to true, then
      * only the display represented by the {@code displayId} parameter will be requested to switch
-     * the touch mode state. Otherwise all all displays will be requested to switch their touch mode
-     * state (disregarding {@code displayId} parameter).
+     * the touch mode state. Otherwise all displays that do not maintain their own focus and touch
+     * mode will be requested to switch their touch mode state (disregarding {@code displayId}
+     * parameter).
      *
      * To be able to change touch mode state, the caller must either own the focused window, or must
      * have the {@link android.Manifest.permission#MODIFY_TOUCH_MODE_STATE} permission. Instrumented
@@ -3857,27 +3858,9 @@
      */
     @Override // Binder call
     public void setInTouchMode(boolean inTouch, int displayId) {
-        boolean perDisplayFocusEnabled = mContext.getResources().getBoolean(
-                com.android.internal.R.bool.config_perDisplayFocusEnabled);
-        setInTouchMode(inTouch, displayId, perDisplayFocusEnabled);
-    }
-
-    /**
-     * Sets the touch mode state on all displays (disregarding the value of
-     * {@code com.android.internal.R.bool.config_perDisplayFocusEnabled}).
-     *
-     * @param inTouch the touch mode to set
-     */
-    @Override // Binder call
-    public void setInTouchModeOnAllDisplays(boolean inTouch) {
-        setInTouchMode(inTouch, /* any display id */ DEFAULT_DISPLAY,
-                /* perDisplayFocusEnabled= */ false);
-    }
-
-    private void setInTouchMode(boolean inTouch, int displayId, boolean perDisplayFocusEnabled) {
         synchronized (mGlobalLock) {
             final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
-            if (perDisplayFocusEnabled && (displayContent == null
+            if (mPerDisplayFocusEnabled && (displayContent == null
                     || displayContent.isInTouchMode() == inTouch)) {
                 return;
             }
@@ -3888,15 +3871,12 @@
             }
             final int pid = Binder.getCallingPid();
             final int uid = Binder.getCallingUid();
-            final boolean hasPermission =
-                    mAtmService.instrumentationSourceHasPermission(pid, MODIFY_TOUCH_MODE_STATE)
-                            || checkCallingPermission(MODIFY_TOUCH_MODE_STATE, "setInTouchMode()",
-                            /* printlog= */ false);
+            final boolean hasPermission = hasTouchModePermission(pid);
             final long token = Binder.clearCallingIdentity();
             try {
-                // If perDisplayFocusEnabled is set or the display maintains its own touch mode,
+                // If mPerDisplayFocusEnabled is set or the display maintains its own touch mode,
                 // then just update the display pointed by displayId
-                if (perDisplayFocusEnabled || displayHasOwnTouchMode) {
+                if (mPerDisplayFocusEnabled || displayHasOwnTouchMode) {
                     if (mInputManager.setInTouchMode(inTouch, pid, uid, hasPermission, displayId)) {
                         displayContent.setInTouchMode(inTouch);
                     }
@@ -3920,6 +3900,41 @@
     }
 
     /**
+     * Sets the touch mode state forcibly on all displays (disregarding both the value of
+     * {@code com.android.internal.R.bool.config_perDisplayFocusEnabled} and whether the display
+     * maintains its own focus and touch mode).
+     *
+     * @param inTouch the touch mode to set
+     */
+    @Override // Binder call
+    public void setInTouchModeOnAllDisplays(boolean inTouch) {
+        final int pid = Binder.getCallingPid();
+        final int uid = Binder.getCallingUid();
+        final boolean hasPermission = hasTouchModePermission(pid);
+        final long token = Binder.clearCallingIdentity();
+        try {
+            synchronized (mGlobalLock) {
+                for (int i = 0; i < mRoot.mChildren.size(); ++i) {
+                    DisplayContent dc = mRoot.mChildren.get(i);
+                    if (dc.isInTouchMode() != inTouch
+                            && mInputManager.setInTouchMode(inTouch, pid, uid, hasPermission,
+                            dc.mDisplayId)) {
+                        dc.setInTouchMode(inTouch);
+                    }
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    private boolean hasTouchModePermission(int pid) {
+        return mAtmService.instrumentationSourceHasPermission(pid, MODIFY_TOUCH_MODE_STATE)
+                || checkCallingPermission(MODIFY_TOUCH_MODE_STATE, "setInTouchMode()",
+                /* printlog= */ false);
+    }
+
+    /**
      * Returns the touch mode state for the display id passed as argument.
      */
     @Override  // Binder call
@@ -8030,6 +8045,19 @@
         }
 
         @Override
+        public void setWallpaperShowWhenLocked(IBinder binder, boolean showWhenLocked) {
+            synchronized (mGlobalLock) {
+                final WindowToken token = mRoot.getWindowToken(binder);
+                if (token == null || token.asWallpaperToken() == null) {
+                    ProtoLog.w(WM_ERROR,
+                            "setWallpaperShowWhenLocked: non-existent wallpaper token: %s", binder);
+                    return;
+                }
+                token.asWallpaperToken().setShowWhenLocked(showWhenLocked);
+            }
+        }
+
+        @Override
         public boolean isUidFocused(int uid) {
             synchronized (mGlobalLock) {
                 for (int i = mRoot.getChildCount() - 1; i >= 0; i--) {
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 3faf9e0..f9592cc 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -29,6 +29,7 @@
 import static android.window.TaskFragmentOperation.OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS;
 import static android.window.TaskFragmentOperation.OP_TYPE_SET_ANIMATION_PARAMS;
 import static android.window.TaskFragmentOperation.OP_TYPE_SET_COMPANION_TASK_FRAGMENT;
+import static android.window.TaskFragmentOperation.OP_TYPE_SET_RELATIVE_BOUNDS;
 import static android.window.TaskFragmentOperation.OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT;
 import static android.window.TaskFragmentOperation.OP_TYPE_UNKNOWN;
 import static android.window.WindowContainerTransaction.Change.CHANGE_RELATIVE_BOUNDS;
@@ -94,6 +95,7 @@
 import android.window.TaskFragmentAnimationParams;
 import android.window.TaskFragmentCreationParams;
 import android.window.TaskFragmentOperation;
+import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -494,8 +496,8 @@
         mService.deferWindowLayout();
         mService.mTaskSupervisor.setDeferRootVisibilityUpdate(true /* deferUpdate */);
         try {
-            if (transition != null && transition.applyDisplayChangeIfNeeded()) {
-                effects |= TRANSACT_EFFECTS_LIFECYCLE;
+            if (transition != null) {
+                transition.applyDisplayChangeIfNeeded();
             }
             final List<WindowContainerTransaction.HierarchyOp> hops = t.getHierarchyOps();
             final int hopSize = hops.size();
@@ -784,8 +786,8 @@
         taskFragment.deferOrganizedTaskFragmentSurfaceUpdate();
         final Rect relBounds = c.getRelativeBounds();
         if (relBounds != null) {
-            // Make sure the TaskFragment bounds satisfied the min dimensions requirement.
-            adjustTaskFragmentBoundsForMinDimensionsIfNeeded(taskFragment, relBounds,
+            // Make sure the requested bounds satisfied the min dimensions requirement.
+            adjustTaskFragmentRelativeBoundsForMinDimensionsIfNeeded(taskFragment, relBounds,
                     errorCallbackToken);
 
             // For embedded TaskFragment, the organizer set the bounds in parent coordinate to
@@ -797,13 +799,6 @@
                     parentBounds);
             c.getConfiguration().windowConfiguration.setBounds(absBounds);
             taskFragment.setRelativeEmbeddedBounds(relBounds);
-        } else if ((c.getWindowSetMask() & WINDOW_CONFIG_BOUNDS) != 0) {
-            // TODO(b/265271880): remove after we drop support to setBounds for TaskFragment in next
-            // release.
-            adjustTaskFragmentBoundsForMinDimensionsIfNeeded(taskFragment, c.getConfiguration()
-                    .windowConfiguration.getBounds(), errorCallbackToken);
-            // Reset the relative embedded bounds if WCT#setBounds is used instead for CTS compat.
-            taskFragment.setRelativeEmbeddedBounds(new Rect());
         }
         final int effects = applyChanges(taskFragment, c);
         if (taskFragment.shouldStartChangeTransition(mTmpBounds0, mTmpBounds1)) {
@@ -814,25 +809,27 @@
     }
 
     /**
-     * Adjusts the override absolute bounds on {@link TaskFragment} to make sure it satisfies the
+     * Adjusts the requested relative bounds on {@link TaskFragment} to make sure it satisfies the
      * activity min dimensions.
      */
-    private void adjustTaskFragmentBoundsForMinDimensionsIfNeeded(
-            @NonNull TaskFragment taskFragment, @NonNull Rect inOutBounds,
+    private void adjustTaskFragmentRelativeBoundsForMinDimensionsIfNeeded(
+            @NonNull TaskFragment taskFragment, @NonNull Rect inOutRelativeBounds,
             @Nullable IBinder errorCallbackToken) {
-        if (inOutBounds.isEmpty()) {
+        if (inOutRelativeBounds.isEmpty()) {
             return;
         }
         final Point minDimensions = taskFragment.calculateMinDimension();
-        if (inOutBounds.width() < minDimensions.x || inOutBounds.height() < minDimensions.y) {
-            // Reset to match parent bounds.
-            inOutBounds.setEmpty();
+        if (inOutRelativeBounds.width() < minDimensions.x
+                || inOutRelativeBounds.height() < minDimensions.y) {
             // Notify organizer about the request failure.
-            final Throwable exception = new SecurityException("The task fragment's bounds:"
-                    + taskFragment.getBounds() + " does not satisfy minimum dimensions:"
+            final Throwable exception = new SecurityException("The requested relative bounds:"
+                    + inOutRelativeBounds + " does not satisfy minimum dimensions:"
                     + minDimensions);
             sendTaskFragmentOperationFailure(taskFragment.getTaskFragmentOrganizer(),
-                    errorCallbackToken, taskFragment, OP_TYPE_UNKNOWN, exception);
+                    errorCallbackToken, taskFragment, OP_TYPE_SET_RELATIVE_BOUNDS, exception);
+
+            // Reset to match parent bounds.
+            inOutRelativeBounds.setEmpty();
         }
     }
 
@@ -1726,9 +1723,7 @@
                 t.getChanges().entrySet().iterator();
         while (entries.hasNext()) {
             final Map.Entry<IBinder, WindowContainerTransaction.Change> entry = entries.next();
-            // Only allow to apply changes to TaskFragment that is created by this organizer.
             final WindowContainer wc = WindowContainer.fromBinder(entry.getKey());
-            enforceTaskFragmentOrganized(func, wc, organizer);
             enforceTaskFragmentConfigChangeAllowed(func, wc, entry.getValue(), organizer);
         }
 
@@ -1764,27 +1759,6 @@
     }
 
     /**
-     * Makes sure that the given {@link WindowContainer} is a {@link TaskFragment} organized by the
-     * given {@link ITaskFragmentOrganizer}.
-     */
-    private void enforceTaskFragmentOrganized(@NonNull String func, @Nullable WindowContainer wc,
-            @NonNull ITaskFragmentOrganizer organizer) {
-        if (wc == null) {
-            Slog.e(TAG, "Attempt to operate on window that no longer exists");
-            return;
-        }
-
-        final TaskFragment tf = wc.asTaskFragment();
-        if (tf == null || !tf.hasTaskFragmentOrganizer(organizer)) {
-            String msg = "Permission Denial: " + func + " from pid=" + Binder.getCallingPid()
-                    + ", uid=" + Binder.getCallingUid() + " trying to modify window container not"
-                    + " belonging to the TaskFragmentOrganizer=" + organizer;
-            Slog.w(TAG, msg);
-            throw new SecurityException(msg);
-        }
-    }
-
-    /**
      * Makes sure that the {@link TaskFragment} of the given fragment token is created and organized
      * by the given {@link ITaskFragmentOrganizer}.
      */
@@ -1805,81 +1779,49 @@
     }
 
     /**
-     * Makes sure that SurfaceControl transactions and the ability to set bounds outside of the
-     * parent bounds are not allowed for embedding without full trust between the host and the
-     * target.
+     * For config change on {@link TaskFragment}, we only support the following operations:
+     * {@link WindowContainerTransaction#setRelativeBounds(WindowContainerToken, Rect)},
+     * {@link WindowContainerTransaction#setWindowingMode(WindowContainerToken, int)}.
      */
-    private void enforceTaskFragmentConfigChangeAllowed(String func, @Nullable WindowContainer wc,
-            WindowContainerTransaction.Change change, ITaskFragmentOrganizer organizer) {
+    private void enforceTaskFragmentConfigChangeAllowed(@NonNull String func,
+            @Nullable WindowContainer wc, @NonNull WindowContainerTransaction.Change change,
+            @NonNull ITaskFragmentOrganizer organizer) {
         if (wc == null) {
             Slog.e(TAG, "Attempt to operate on task fragment that no longer exists");
             return;
         }
-        if (change == null) {
+        final TaskFragment tf = wc.asTaskFragment();
+        if (tf == null || !tf.hasTaskFragmentOrganizer(organizer)) {
+            // Only allow to apply changes to TaskFragment that is organized by this organizer.
+            String msg = "Permission Denial: " + func + " from pid=" + Binder.getCallingPid()
+                    + ", uid=" + Binder.getCallingUid() + " trying to modify window container"
+                    + " not belonging to the TaskFragmentOrganizer=" + organizer;
+            Slog.w(TAG, msg);
+            throw new SecurityException(msg);
+        }
+
+        final int changeMask = change.getChangeMask();
+        final int configSetMask = change.getConfigSetMask();
+        final int windowSetMask = change.getWindowSetMask();
+        if (changeMask == 0 && configSetMask == 0 && windowSetMask == 0
+                && change.getWindowingMode() >= 0) {
+            // The change contains only setWindowingMode, which is allowed.
             return;
         }
-        final int changeMask = change.getChangeMask();
-        if (changeMask != 0 && changeMask != CHANGE_RELATIVE_BOUNDS) {
+        if (changeMask != CHANGE_RELATIVE_BOUNDS
+                || configSetMask != ActivityInfo.CONFIG_WINDOW_CONFIGURATION
+                || windowSetMask != WindowConfiguration.WINDOW_CONFIG_BOUNDS) {
             // None of the change should be requested from a TaskFragment organizer except
-            // setRelativeBounds.
+            // setRelativeBounds and setWindowingMode.
             // For setRelativeBounds, we don't need to check whether it is outside of the Task
             // bounds, because it is possible that the Task is also resizing, for which we don't
             // want to throw an exception. The bounds will be adjusted in
             // TaskFragment#translateRelativeBoundsToAbsoluteBounds.
             String msg = "Permission Denial: " + func + " from pid="
                     + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
-                    + " trying to apply changes of " + changeMask + " to TaskFragment"
-                    + " TaskFragmentOrganizer=" + organizer;
-            Slog.w(TAG, msg);
-            throw new SecurityException(msg);
-        }
-        // Check if TaskFragment is embedded in fully trusted mode.
-        if (wc.asTaskFragment().isAllowedToBeEmbeddedInTrustedMode()) {
-            // Fully trusted, no need to check further
-            return;
-        }
-        final WindowContainer wcParent = wc.getParent();
-        if (wcParent == null) {
-            Slog.e(TAG, "Attempt to apply config change on task fragment that has no parent");
-            return;
-        }
-        // TODO(b/265271880): we can remove those and only support WCT#setRelativeBounds.
-        final Configuration requestedConfig = change.getConfiguration();
-        final Configuration parentConfig = wcParent.getConfiguration();
-        if (parentConfig.screenWidthDp < requestedConfig.screenWidthDp
-                || parentConfig.screenHeightDp < requestedConfig.screenHeightDp
-                || parentConfig.smallestScreenWidthDp < requestedConfig.smallestScreenWidthDp) {
-            String msg = "Permission Denial: " + func + " from pid="
-                    + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
-                    + " trying to apply screen width/height greater than parent's for non-trusted"
-                    + " host, TaskFragmentOrganizer=" + organizer;
-            Slog.w(TAG, msg);
-            throw new SecurityException(msg);
-        }
-        if (change.getWindowSetMask() == 0) {
-            // No bounds change.
-            return;
-        }
-        final WindowConfiguration requestedWindowConfig = requestedConfig.windowConfiguration;
-        final WindowConfiguration parentWindowConfig = parentConfig.windowConfiguration;
-        if (!requestedWindowConfig.getBounds().isEmpty()
-                && !parentWindowConfig.getBounds().contains(requestedWindowConfig.getBounds())) {
-            String msg = "Permission Denial: " + func + " from pid="
-                    + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
-                    + " trying to apply bounds outside of parent for non-trusted host,"
-                    + " TaskFragmentOrganizer=" + organizer;
-            Slog.w(TAG, msg);
-            throw new SecurityException(msg);
-        }
-        if (requestedWindowConfig.getAppBounds() != null
-                && !requestedWindowConfig.getAppBounds().isEmpty()
-                && parentWindowConfig.getAppBounds() != null
-                && !parentWindowConfig.getAppBounds().contains(
-                        requestedWindowConfig.getAppBounds())) {
-            String msg = "Permission Denial: " + func + " from pid="
-                    + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
-                    + " trying to apply app bounds outside of parent for non-trusted host,"
-                    + " TaskFragmentOrganizer=" + organizer;
+                    + " trying to apply changes of changeMask=" + changeMask
+                    + " configSetMask=" + configSetMask + " windowSetMask=" + windowSetMask
+                    + " to TaskFragment=" + tf + " TaskFragmentOrganizer=" + organizer;
             Slog.w(TAG, msg);
             throw new SecurityException(msg);
         }
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index bf6bab3..69e84c7 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2067,12 +2067,10 @@
             final boolean exiting = mAnimatingExit || mDestroying;
             return shown && !exiting;
         } else {
-            final Task task = getTask();
-            final boolean canFromTask = task != null && task.canAffectSystemUiFlags();
-            return canFromTask && mActivityRecord.isVisible()
-            // Do not let snapshot window control the bar
+            return mActivityRecord.canAffectSystemUiFlags()
+                    // Do not let snapshot window control the bar
                     && (mAttrs.type != TYPE_APPLICATION_STARTING
-                    || !(mStartingData instanceof SnapshotStartingData));
+                            || !(mStartingData instanceof SnapshotStartingData));
         }
     }
 
@@ -5665,14 +5663,6 @@
                     && imeTarget.compareTo(this) <= 0;
             return inTokenWithAndAboveImeTarget;
         }
-
-        // The condition is for the system dialog not belonging to any Activity.
-        // (^FLAG_NOT_FOCUSABLE & FLAG_ALT_FOCUSABLE_IM) means the dialog is still focusable but
-        // should be placed above the IME window.
-        if ((mAttrs.flags & (FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM))
-                == FLAG_ALT_FOCUSABLE_IM && isTrustedOverlay() && canAddInternalSystemWindow()) {
-            return true;
-        }
         return false;
     }
 
diff --git a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
index 2345e3f..c8518c5 100644
--- a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
@@ -97,7 +97,7 @@
         if (response != null) {
             respondToClientWithResponseAndFinish(response);
         } else {
-            respondToClientWithErrorAndFinish(CreateCredentialException.TYPE_NO_CREDENTIAL,
+            respondToClientWithErrorAndFinish(CreateCredentialException.TYPE_NO_CREATE_OPTIONS,
                     "Invalid response");
         }
     }
@@ -119,6 +119,12 @@
         }
     }
 
+    @Override
+    public void onUiSelectorInvocationFailure() {
+        respondToClientWithErrorAndFinish(CreateCredentialException.TYPE_NO_CREATE_OPTIONS,
+                "No create options available.");
+    }
+
     private void respondToClientWithResponseAndFinish(CreateCredentialResponse response) {
         Log.i(TAG, "respondToClientWithResponseAndFinish");
         if (isSessionCancelled()) {
@@ -166,8 +172,8 @@
                 Log.i(TAG, "in onProviderStatusChanged - isUiInvocationNeeded");
                 getProviderDataAndInitiateUi();
             } else {
-                respondToClientWithErrorAndFinish(CreateCredentialException.TYPE_NO_CREDENTIAL,
-                        "No credentials available");
+                respondToClientWithErrorAndFinish(CreateCredentialException.TYPE_NO_CREATE_OPTIONS,
+                        "No create options available.");
             }
         }
     }
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index 6857239..79f619c 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -16,10 +16,12 @@
 
 package com.android.server.credentials;
 
+import static android.Manifest.permission.CREDENTIAL_MANAGER_SET_ORIGIN;
 import static android.content.Context.CREDENTIAL_SERVICE;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.content.ComponentName;
@@ -316,22 +318,26 @@
         CredentialDescriptionRegistry.clearUserSession(user.getUserIdentifier());
     }
 
-    private CallingAppInfo constructCallingAppInfo(String packageName, int userId) {
+    private CallingAppInfo constructCallingAppInfo(
+            String realPackageName,
+            int userId,
+            @Nullable String origin) {
         final PackageInfo packageInfo;
+        String actualPackageName = origin == null ? realPackageName : origin;
         try {
             packageInfo =
-                    getContext()
-                            .getPackageManager()
-                            .getPackageInfoAsUser(
-                                    packageName,
-                                    PackageManager.PackageInfoFlags.of(
-                                            PackageManager.GET_SIGNING_CERTIFICATES),
-                                    userId);
+                getContext()
+                    .getPackageManager()
+                    .getPackageInfoAsUser(
+                        actualPackageName,
+                        PackageManager.PackageInfoFlags.of(
+                            PackageManager.GET_SIGNING_CERTIFICATES),
+                        userId);
         } catch (PackageManager.NameNotFoundException e) {
             Log.i(TAG, "Issue while retrieving signatureInfo : " + e.getMessage());
-            return new CallingAppInfo(packageName, null);
+            return new CallingAppInfo(actualPackageName, null);
         }
-        return new CallingAppInfo(packageName, packageInfo.signingInfo);
+        return new CallingAppInfo(actualPackageName, packageInfo.signingInfo);
     }
 
     final class CredentialManagerServiceStub extends ICredentialManager.Stub {
@@ -355,59 +361,97 @@
                             callingUid,
                             callback,
                             request,
-                            constructCallingAppInfo(callingPackage, userId),
+                            constructCallingAppInfo(callingPackage, userId, null),
                             CancellationSignal.fromTransport(cancelTransport));
 
+            processGetCredential(request, callback, session);
+            return cancelTransport;
+        }
+
+        public ICancellationSignal executeGetCredentialWithOrigin(
+                GetCredentialRequest request,
+                IGetCredentialCallback callback,
+                final String callingPackage,
+                final String origin) {
+            Log.i(TAG, "starting executeGetCredential with callingPackage: " + callingPackage);
+            ICancellationSignal cancelTransport = CancellationSignal.createTransport();
+
+            // Check privileged permissions
+            mContext.enforceCallingPermission(CREDENTIAL_MANAGER_SET_ORIGIN, null);
+
+            final int userId = UserHandle.getCallingUserId();
+            final int callingUid = Binder.getCallingUid();
+            enforceCallingPackage(callingPackage, callingUid);
+
+            // New request session, scoped for this request only.
+            final GetRequestSession session =
+                    new GetRequestSession(
+                        getContext(),
+                        userId,
+                        callingUid,
+                        callback,
+                        request,
+                        constructCallingAppInfo(callingPackage, userId, origin),
+                        CancellationSignal.fromTransport(cancelTransport));
+
+            processGetCredential(request, callback, session);
+            return cancelTransport;
+        }
+
+        private void processGetCredential(
+                GetCredentialRequest request,
+                IGetCredentialCallback callback,
+                GetRequestSession session) {
             List<ProviderSession> providerSessions;
 
             // TODO(b/268143699): temporarily disable the flag due to bug.
             if (false) {
                 List<CredentialOption> optionsThatRequireActiveCredentials =
                         request.getCredentialOptions().stream()
-                                .filter(
-                                        getCredentialOption ->
-                                                !TextUtils.isEmpty(
-                                                        getCredentialOption
-                                                                .getCredentialRetrievalData()
-                                                                .getString(
-                                                                        CredentialOption
-                                                                                .FLATTENED_REQUEST,
-                                                                        null)))
-                                .toList();
+                        .filter(
+                            getCredentialOption ->
+                                !TextUtils.isEmpty(
+                                    getCredentialOption
+                                        .getCredentialRetrievalData()
+                                        .getString(
+                                            CredentialOption
+                                                .FLATTENED_REQUEST,
+                                            null)))
+                        .toList();
 
                 List<CredentialOption> optionsThatDoNotRequireActiveCredentials =
                         request.getCredentialOptions().stream()
-                                .filter(
-                                        getCredentialOption ->
-                                                TextUtils.isEmpty(
-                                                        getCredentialOption
-                                                                .getCredentialRetrievalData()
-                                                                .getString(
-                                                                        CredentialOption
-                                                                                .FLATTENED_REQUEST,
-                                                                        null)))
-                                .toList();
+                        .filter(
+                            getCredentialOption ->
+                                TextUtils.isEmpty(
+                                    getCredentialOption
+                                        .getCredentialRetrievalData()
+                                        .getString(
+                                            CredentialOption
+                                                .FLATTENED_REQUEST,
+                                            null)))
+                        .toList();
 
                 List<ProviderSession> sessionsWithoutRemoteService =
                         initiateProviderSessionsWithActiveContainers(
-                                session,
-                                optionsThatRequireActiveCredentials.stream()
-                                        .map(
-                                                getCredentialOption ->
-                                                        getCredentialOption
-                                                                .getCredentialRetrievalData()
-                                                                .getString(
-                                                                        CredentialOption
-                                                                                .FLATTENED_REQUEST))
-                                        .collect(Collectors.toList()),
-                                getFilteredResultFromRegistry(optionsThatRequireActiveCredentials));
+                        session,
+                        optionsThatRequireActiveCredentials.stream()
+                            .map(
+                                getCredentialOption ->
+                                    getCredentialOption
+                                        .getCredentialRetrievalData()
+                                        .getString(
+                                            CredentialOption
+                                                .FLATTENED_REQUEST))
+                            .collect(Collectors.toList()),
+                        getFilteredResultFromRegistry(optionsThatRequireActiveCredentials));
 
                 List<ProviderSession> sessionsWithRemoteService =
                         initiateProviderSessions(
-                                session,
-                                optionsThatDoNotRequireActiveCredentials.stream()
-                                        .map(CredentialOption::getType)
-                                        .collect(Collectors.toList()));
+                        session,
+                        optionsThatDoNotRequireActiveCredentials.stream()
+                            .map(CredentialOption::getType)
+                            .collect(Collectors.toList()));
 
                 Set<ProviderSession> all = new LinkedHashSet<>();
                 all.addAll(sessionsWithRemoteService);
@@ -417,11 +461,11 @@
             } else {
                 // Initiate all provider sessions
                 providerSessions =
-                        initiateProviderSessions(
-                                session,
-                                request.getCredentialOptions().stream()
-                                        .map(CredentialOption::getType)
-                                        .collect(Collectors.toList()));
+                    initiateProviderSessions(
+                        session,
+                        request.getCredentialOptions().stream()
+                            .map(CredentialOption::getType)
+                            .collect(Collectors.toList()));
             }
 
             if (providerSessions.isEmpty()) {
@@ -433,13 +477,11 @@
                     Log.i(
                             TAG,
                             "Issue invoking onError on IGetCredentialCallback "
-                                    + "callback: "
-                                    + e.getMessage());
+                                + "callback: "
+                                + e.getMessage());
                 }
             }
             providerSessions.forEach(ProviderSession::invokeSession);
-
-            return cancelTransport;
         }
 
         @Override
@@ -462,9 +504,47 @@
                             callingUid,
                             request,
                             callback,
-                            constructCallingAppInfo(callingPackage, userId),
+                            constructCallingAppInfo(callingPackage, userId, null),
                             CancellationSignal.fromTransport(cancelTransport));
 
+            processCreateCredential(request, callback, session);
+            return cancelTransport;
+        }
+
+        public ICancellationSignal executeCreateCredentialWithOrigin(
+                CreateCredentialRequest request,
+                ICreateCredentialCallback callback,
+                String callingPackage,
+                String origin) {
+            Log.i(TAG, "starting executeCreateCredential with callingPackage: " + callingPackage);
+            ICancellationSignal cancelTransport = CancellationSignal.createTransport();
+
+            // Check privileged permissions
+            mContext.enforceCallingPermission(CREDENTIAL_MANAGER_SET_ORIGIN, null);
+
+            final int userId = UserHandle.getCallingUserId();
+            final int callingUid = Binder.getCallingUid();
+            enforceCallingPackage(callingPackage, callingUid);
+
+            // New request session, scoped for this request only.
+            final CreateRequestSession session =
+                    new CreateRequestSession(
+                        getContext(),
+                        userId,
+                        callingUid,
+                        request,
+                        callback,
+                        constructCallingAppInfo(callingPackage, userId, origin),
+                        CancellationSignal.fromTransport(cancelTransport));
+
+            processCreateCredential(request, callback, session);
+            return cancelTransport;
+        }
+
+        private void processCreateCredential(
+                CreateCredentialRequest request,
+                ICreateCredentialCallback callback,
+                CreateRequestSession session) {
             // Initiate all provider sessions
             List<ProviderSession> providerSessions =
                     initiateProviderSessions(session, List.of(request.getType()));
@@ -472,20 +552,19 @@
             if (providerSessions.isEmpty()) {
                 try {
                     callback.onError(
-                            CreateCredentialException.TYPE_NO_CREDENTIAL,
-                            "No credentials available on this device.");
+                            CreateCredentialException.TYPE_NO_CREATE_OPTIONS,
+                            "No create options available.");
                 } catch (RemoteException e) {
                     Log.i(
                             TAG,
                             "Issue invoking onError on ICreateCredentialCallback "
-                                    + "callback: "
-                                    + e.getMessage());
+                                + "callback: "
+                                + e.getMessage());
                 }
             }
 
             // Iterate over all provider sessions and invoke the request
             providerSessions.forEach(ProviderSession::invokeSession);
-            return cancelTransport;
         }
 
         @SuppressWarnings("GuardedBy") // ErrorProne requires listEnabledProviders
@@ -630,7 +709,7 @@
                             callingUid,
                             callback,
                             request,
-                            constructCallingAppInfo(callingPackage, userId),
+                            constructCallingAppInfo(callingPackage, userId, null),
                             CancellationSignal.fromTransport(cancelTransport));
 
             // Initiate all provider sessions
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
index 797601a..a6f6a830 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
@@ -56,19 +56,29 @@
     };
 
     private void handleUiResult(int resultCode, Bundle resultData) {
-        if (resultCode == UserSelectionDialogResult.RESULT_CODE_DIALOG_COMPLETE_WITH_SELECTION) {
-            UserSelectionDialogResult selection = UserSelectionDialogResult
-                    .fromResultData(resultData);
-            if (selection != null) {
-                mCallbacks.onUiSelection(selection);
-            } else {
-                Slog.i(TAG, "No selection found in UI result");
-            }
-        } else if (resultCode == UserSelectionDialogResult.RESULT_CODE_DIALOG_USER_CANCELED) {
-            mCallbacks.onUiCancellation(/* isUserCancellation= */ true);
-        } else if (resultCode
-                == UserSelectionDialogResult.RESULT_CODE_CANCELED_AND_LAUNCHED_SETTINGS) {
-            mCallbacks.onUiCancellation(/* isUserCancellation= */ false);
+        switch (resultCode) {
+            case UserSelectionDialogResult.RESULT_CODE_DIALOG_COMPLETE_WITH_SELECTION:
+                UserSelectionDialogResult selection = UserSelectionDialogResult
+                        .fromResultData(resultData);
+                if (selection != null) {
+                    mCallbacks.onUiSelection(selection);
+                } else {
+                    Slog.i(TAG, "No selection found in UI result");
+                }
+                break;
+            case UserSelectionDialogResult.RESULT_CODE_DIALOG_USER_CANCELED:
+                mCallbacks.onUiCancellation(/* isUserCancellation= */ true);
+                break;
+            case UserSelectionDialogResult.RESULT_CODE_CANCELED_AND_LAUNCHED_SETTINGS:
+                mCallbacks.onUiCancellation(/* isUserCancellation= */ false);
+                break;
+            case UserSelectionDialogResult.RESULT_CODE_DATA_PARSING_FAILURE:
+                mCallbacks.onUiSelectorInvocationFailure();
+                break;
+            default:
+                Slog.i(TAG, "Unknown error code returned from the UI");
+                mCallbacks.onUiSelectorInvocationFailure();
+                break;
         }
     }
 
@@ -80,6 +90,9 @@
         void onUiSelection(UserSelectionDialogResult selection);
         /** Called when the UI is canceled without a successful provider result. */
         void onUiCancellation(boolean isUserCancellation);
+
+        /** Called when the selector UI fails to come up (mostly due to parsing issue today). */
+        void onUiSelectorInvocationFailure();
     }
     public CredentialManagerUi(Context context, int userId,
             CredentialManagerUiCallback callbacks) {
diff --git a/services/credentials/java/com/android/server/credentials/GetRequestSession.java b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
index e732c23..3324999 100644
--- a/services/credentials/java/com/android/server/credentials/GetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
@@ -147,6 +147,12 @@
     }
 
     @Override
+    public void onUiSelectorInvocationFailure() {
+        respondToClientWithErrorAndFinish(GetCredentialException.TYPE_NO_CREDENTIAL,
+                    "No credentials to show on the selector.");
+    }
+
+    @Override
     public void onProviderStatusChanged(ProviderSession.Status status,
             ComponentName componentName) {
         Log.i(TAG, "in onStatusChanged with status: " + status);
diff --git a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
index 20e358c..b4c4233 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
@@ -77,14 +77,19 @@
                         createRequestSession.mClientRequest,
                         createRequestSession.mClientAppInfo);
         if (providerCreateRequest != null) {
-            BeginCreateCredentialRequest providerBeginCreateRequest =
+            return new ProviderCreateSession(
+                    context,
+                    providerInfo,
+                    createRequestSession,
+                    userId,
+                    remoteCredentialService,
                     constructQueryPhaseRequest(createRequestSession.mClientRequest.getType(),
                             createRequestSession.mClientRequest.getCandidateQueryData(),
                             createRequestSession.mClientAppInfo,
                             createRequestSession
-                                    .mClientRequest.alwaysSendAppInfoToProvider());
-            return new ProviderCreateSession(context, providerInfo, createRequestSession, userId,
-                    remoteCredentialService, providerBeginCreateRequest, providerCreateRequest);
+                                    .mClientRequest.alwaysSendAppInfoToProvider()),
+                    providerCreateRequest
+            );
         }
         Log.i(TAG, "Unable to create provider session");
         return null;
@@ -96,13 +101,14 @@
         if (propagateToProvider) {
             return new BeginCreateCredentialRequest(
                     type,
-                    candidateQueryData
+                    candidateQueryData,
+                    callingAppInfo
             );
         }
         return new BeginCreateCredentialRequest(
                 type,
-                candidateQueryData,
-                callingAppInfo);
+                candidateQueryData
+        );
     }
 
     @Nullable
@@ -168,7 +174,16 @@
     private void onUpdateResponse(BeginCreateCredentialResponse response) {
         Log.i(TAG, "updateResponse with save entries");
         mProviderResponse = response;
-        updateStatusAndInvokeCallback(Status.SAVE_ENTRIES_RECEIVED);
+        if (isEmptyResponse(response)) {
+            updateStatusAndInvokeCallback(Status.EMPTY_RESPONSE);
+        } else {
+            updateStatusAndInvokeCallback(Status.SAVE_ENTRIES_RECEIVED);
+        }
+    }
+
+    private boolean isEmptyResponse(BeginCreateCredentialResponse response) {
+        return (response.getCreateEntries() == null || response.getCreateEntries().isEmpty())
+                && response.getRemoteCreateEntry() == null;
     }
 
     @Override
@@ -294,7 +309,7 @@
             ProviderPendingIntentResponse pendingIntentResponse) {
         if (pendingIntentResponse == null) {
             Log.i(TAG, "pendingIntentResponse is null");
-            return new CreateCredentialException(CreateCredentialException.TYPE_NO_CREDENTIAL);
+            return new CreateCredentialException(CreateCredentialException.TYPE_NO_CREATE_OPTIONS);
         }
         if (PendingIntentResultHandler.isValidResponse(pendingIntentResponse)) {
             CreateCredentialException exception = PendingIntentResultHandler
@@ -306,7 +321,7 @@
         } else if (PendingIntentResultHandler.isCancelledResponse(pendingIntentResponse)) {
             return new CreateCredentialException(CreateCredentialException.TYPE_USER_CANCELED);
         } else {
-            return new CreateCredentialException(CreateCredentialException.TYPE_NO_CREDENTIAL);
+            return new CreateCredentialException(CreateCredentialException.TYPE_NO_CREATE_OPTIONS);
         }
         return null;
     }
diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
index 9fba95b..c2a57a8 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
@@ -24,6 +24,7 @@
 import android.credentials.CredentialOption;
 import android.credentials.GetCredentialException;
 import android.credentials.GetCredentialResponse;
+import android.credentials.ui.AuthenticationEntry;
 import android.credentials.ui.Entry;
 import android.credentials.ui.GetCredentialProviderData;
 import android.credentials.ui.ProviderPendingIntentResponse;
@@ -96,19 +97,24 @@
         if (filteredRequest != null) {
             Map<String, CredentialOption> beginGetOptionToCredentialOptionMap =
                     new HashMap<>();
-            BeginGetCredentialRequest beginGetCredentialRequest = constructQueryPhaseRequest(
-                    filteredRequest, getRequestSession.mClientAppInfo,
-                    getRequestSession.mClientRequest.alwaysSendAppInfoToProvider(),
-                    beginGetOptionToCredentialOptionMap);
-            return new ProviderGetSession(context, providerInfo, getRequestSession, userId,
-                    remoteCredentialService, beginGetCredentialRequest, filteredRequest,
+            return new ProviderGetSession(
+                    context,
+                    providerInfo,
+                    getRequestSession,
+                    userId,
+                    remoteCredentialService,
+                    constructQueryPhaseRequest(
+                            filteredRequest, getRequestSession.mClientAppInfo,
+                            getRequestSession.mClientRequest.alwaysSendAppInfoToProvider(),
+                            beginGetOptionToCredentialOptionMap),
+                    filteredRequest,
                     getRequestSession.mClientAppInfo,
-                    beginGetOptionToCredentialOptionMap);
+                    beginGetOptionToCredentialOptionMap
+            );
         }
         Log.i(TAG, "Unable to create provider session");
         return null;
     }
-
     private static BeginGetCredentialRequest constructQueryPhaseRequest(
             android.credentials.GetCredentialRequest filteredRequest,
             CallingAppInfo callingAppInfo,
@@ -168,7 +174,7 @@
         mCompleteRequest = completeGetRequest;
         mCallingAppInfo = callingAppInfo;
         setStatus(Status.PENDING);
-        mBeginGetOptionToCredentialOptionMap = beginGetOptionToCredentialOptionMap;
+        mBeginGetOptionToCredentialOptionMap = new HashMap<>(beginGetOptionToCredentialOptionMap);
     }
 
     /** Called when the provider response has been updated by an external source. */
@@ -279,16 +285,18 @@
         return remoteEntry;
     }
 
-    private List<Entry> prepareUiAuthenticationEntries(
+    private List<AuthenticationEntry> prepareUiAuthenticationEntries(
             @NonNull List<Action> authenticationEntries) {
-        List<Entry> authenticationUiEntries = new ArrayList<>();
+        List<AuthenticationEntry> authenticationUiEntries = new ArrayList<>();
 
+        // TODO: properly construct entries when they should have the unlocked status.
         for (Action authenticationAction : authenticationEntries) {
             String entryId = generateUniqueId();
             mUiAuthenticationEntries.put(entryId, authenticationAction);
-            authenticationUiEntries.add(new Entry(
+            authenticationUiEntries.add(new AuthenticationEntry(
                     AUTHENTICATION_ACTION_ENTRY_KEY, entryId,
                     authenticationAction.getSlice(),
+                    AuthenticationEntry.STATUS_LOCKED,
                     setUpFillInIntentForAuthentication()));
         }
         return authenticationUiEntries;
@@ -306,18 +314,20 @@
             Log.i(TAG, "in prepareUiProviderData creating ui entry with id " + entryId);
             credentialUiEntries.add(new Entry(CREDENTIAL_ENTRY_KEY, entryId,
                     credentialEntry.getSlice(),
-                    /*fillInIntent=*/setUpFillInIntent(credentialEntry.getType())));
+                    /*fillInIntent=*/setUpFillInIntent(credentialEntry
+                    .getBeginGetCredentialOption().getId())));
         }
         return credentialUiEntries;
     }
 
-    private Intent setUpFillInIntent(@Nullable String id) {
+    private Intent setUpFillInIntent(@NonNull String id) {
         // TODO: Determine if we should skip this entry if entry id is not set, or is set
         // but does not resolve to a valid option. For now, not skipping it because
         // it may be possible that the provider adds their own extras and expects to receive
         // those and complete the flow.
-        if (id == null || mBeginGetOptionToCredentialOptionMap.get(id) == null) {
+        if (mBeginGetOptionToCredentialOptionMap.get(id) == null) {
             Log.i(TAG, "Id from Credential Entry does not resolve to a valid option");
+            return new Intent();
         }
         return new Intent().putExtra(CredentialProviderService.EXTRA_GET_CREDENTIAL_REQUEST,
                 new GetCredentialRequest(
@@ -345,7 +355,7 @@
     }
 
     private GetCredentialProviderData prepareUiProviderData(List<Entry> actionEntries,
-            List<Entry> credentialEntries, List<Entry> authenticationActionEntries,
+            List<Entry> credentialEntries, List<AuthenticationEntry> authenticationActionEntries,
             Entry remoteEntry) {
         return new GetCredentialProviderData.Builder(
                 mComponentName.flattenToString()).setActionChips(actionEntries)
@@ -445,7 +455,22 @@
     /** Updates the response being maintained in state by this provider session. */
     private void onUpdateResponse(BeginGetCredentialResponse response) {
         mProviderResponse = response;
-        updateStatusAndInvokeCallback(Status.CREDENTIALS_RECEIVED);
+        if (isEmptyResponse(response)) {
+            updateStatusAndInvokeCallback(Status.EMPTY_RESPONSE);
+        } else {
+            updateStatusAndInvokeCallback(Status.CREDENTIALS_RECEIVED);
+        }
+    }
+
+    private boolean isEmptyResponse(BeginGetCredentialResponse response) {
+        if ((response.getCredentialEntries() == null || response.getCredentialEntries().isEmpty())
+                && (response.getAuthenticationActions() == null || response
+                .getAuthenticationActions().isEmpty())
+                && (response.getActions() == null || response.getActions().isEmpty())
+                && response.getRemoteCredentialEntry() == null) {
+            return true;
+        }
+        return false;
     }
 
     private void onUpdateEmptyResponse() {
diff --git a/services/credentials/java/com/android/server/credentials/ProviderSession.java b/services/credentials/java/com/android/server/credentials/ProviderSession.java
index 1ae0f3c..1aec934 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderSession.java
@@ -133,7 +133,7 @@
         PENDING_INTENT_INVOKED,
         CREDENTIAL_RECEIVED_FROM_SELECTION,
         SAVE_ENTRIES_RECEIVED, CANCELED,
-        NO_CREDENTIALS, COMPLETE
+        NO_CREDENTIALS, EMPTY_RESPONSE, 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 9f1bd8f..fdd0e81 100644
--- a/services/credentials/java/com/android/server/credentials/RequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/RequestSession.java
@@ -144,6 +144,11 @@
         finishSession(/*propagateCancellation=*/false);
     }
 
+    @Override
+    public void onUiSelectorInvocationFailure() {
+        Log.i(TAG, "onUiSelectorInvocationFailure");
+    }
+
     protected void finishSession(boolean propagateCancellation) {
         Log.i(TAG, "finishing session");
         if (propagateCancellation) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BooleanPolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/BooleanPolicySerializer.java
index 9cb7533..474df98 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/BooleanPolicySerializer.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/BooleanPolicySerializer.java
@@ -18,6 +18,8 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.admin.BooleanPolicyValue;
+import android.app.admin.PolicyKey;
 import android.util.Log;
 
 import com.android.modules.utils.TypedXmlPullParser;
@@ -31,7 +33,8 @@
 final class BooleanPolicySerializer extends PolicySerializer<Boolean> {
 
     @Override
-    void saveToXml(TypedXmlSerializer serializer, String attributeName, @NonNull Boolean value)
+    void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer, String attributeName,
+            @NonNull Boolean value)
             throws IOException {
         Objects.requireNonNull(value);
         serializer.attributeBoolean(/* namespace= */ null, attributeName, value);
@@ -39,9 +42,10 @@
 
     @Nullable
     @Override
-    Boolean readFromXml(TypedXmlPullParser parser, String attributeName) {
+    BooleanPolicyValue readFromXml(TypedXmlPullParser parser, String attributeName) {
         try {
-            return parser.getAttributeBoolean(/* namespace= */ null, attributeName);
+            return new BooleanPolicyValue(
+                    parser.getAttributeBoolean(/* namespace= */ null, attributeName));
         } catch (XmlPullParserException e) {
             Log.e(DevicePolicyEngine.TAG, "Error parsing Boolean policy value", e);
             return null;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BundlePolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/BundlePolicySerializer.java
new file mode 100644
index 0000000..c79aac7
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/BundlePolicySerializer.java
@@ -0,0 +1,260 @@
+/*
+ * 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.server.devicepolicy;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.admin.BundlePolicyValue;
+import android.app.admin.PackagePolicyKey;
+import android.app.admin.PolicyKey;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.Parcelable;
+import android.util.AtomicFile;
+import android.util.Slog;
+import android.util.Xml;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import libcore.io.IoUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Objects;
+
+// TODO(b/266704763): clean this up and stop creating separate files for each value, the code here
+//  is copied from UserManagerService, however it doesn't currently handle setting different
+//  restrictions for the same package in different users, it also will not remove the files for
+//  outdated restrictions, this will all get fixed when we save it as part of the policies file
+//  rather than in its own files.
+final class BundlePolicySerializer extends PolicySerializer<Bundle> {
+
+    private static final String RESTRICTIONS_FILE_PREFIX = "AppRestrictions_";
+    private static final String XML_SUFFIX = ".xml";
+
+    private static final String TAG_RESTRICTIONS = "restrictions";
+    private static final String TAG_ENTRY = "entry";
+    private static final String TAG_VALUE = "value";
+    private static final String ATTR_KEY = "key";
+    private static final String ATTR_VALUE_TYPE = "type";
+    private static final String ATTR_MULTIPLE = "m";
+
+    private static final String ATTR_TYPE_STRING_ARRAY = "sa";
+    private static final String ATTR_TYPE_STRING = "s";
+    private static final String ATTR_TYPE_BOOLEAN = "b";
+    private static final String ATTR_TYPE_INTEGER = "i";
+    private static final String ATTR_TYPE_BUNDLE = "B";
+    private static final String ATTR_TYPE_BUNDLE_ARRAY = "BA";
+
+    @Override
+    void saveToXml(@NonNull PolicyKey policyKey, TypedXmlSerializer serializer,
+            String attributeName, @NonNull Bundle value) throws IOException {
+        Objects.requireNonNull(value);
+        Objects.requireNonNull(policyKey);
+        if (!(policyKey instanceof PackagePolicyKey)) {
+            throw new IllegalArgumentException("policyKey is not of type "
+                    + "PackagePolicyKey");
+        }
+        String packageName = ((PackagePolicyKey) policyKey).getPackageName();
+        String fileName = packageToRestrictionsFileName(packageName, value);
+        writeApplicationRestrictionsLAr(fileName, value);
+        serializer.attribute(/* namespace= */ null, attributeName, fileName);
+    }
+
+    @Nullable
+    @Override
+    BundlePolicyValue readFromXml(TypedXmlPullParser parser, String attributeName) {
+        String fileName = parser.getAttributeValue(/* namespace= */ null, attributeName);
+
+        return new BundlePolicyValue(readApplicationRestrictions(fileName));
+    }
+
+    private static String packageToRestrictionsFileName(String packageName, Bundle restrictions) {
+        return RESTRICTIONS_FILE_PREFIX + packageName + Objects.hash(restrictions) + XML_SUFFIX;
+    }
+
+    @GuardedBy("mAppRestrictionsLock")
+    private static Bundle readApplicationRestrictions(String fileName) {
+        AtomicFile restrictionsFile =
+                new AtomicFile(new File(Environment.getDataSystemDirectory(), fileName));
+        return readApplicationRestrictions(restrictionsFile);
+    }
+
+    @VisibleForTesting
+    @GuardedBy("mAppRestrictionsLock")
+    static Bundle readApplicationRestrictions(AtomicFile restrictionsFile) {
+        final Bundle restrictions = new Bundle();
+        final ArrayList<String> values = new ArrayList<>();
+        if (!restrictionsFile.getBaseFile().exists()) {
+            return restrictions;
+        }
+
+        FileInputStream fis = null;
+        try {
+            fis = restrictionsFile.openRead();
+            final TypedXmlPullParser parser = Xml.resolvePullParser(fis);
+            XmlUtils.nextElement(parser);
+            if (parser.getEventType() != XmlPullParser.START_TAG) {
+                Slog.e(DevicePolicyEngine.TAG, "Unable to read restrictions file "
+                        + restrictionsFile.getBaseFile());
+                return restrictions;
+            }
+            while (parser.next() != XmlPullParser.END_DOCUMENT) {
+                readEntry(restrictions, values, parser);
+            }
+        } catch (IOException | XmlPullParserException e) {
+            Slog.w(DevicePolicyEngine.TAG, "Error parsing " + restrictionsFile.getBaseFile(), e);
+        } finally {
+            IoUtils.closeQuietly(fis);
+        }
+        return restrictions;
+    }
+
+    private static void readEntry(Bundle restrictions, ArrayList<String> values,
+            TypedXmlPullParser parser) throws XmlPullParserException, IOException {
+        int type = parser.getEventType();
+        if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_ENTRY)) {
+            String key = parser.getAttributeValue(null, ATTR_KEY);
+            String valType = parser.getAttributeValue(null, ATTR_VALUE_TYPE);
+            int count = parser.getAttributeInt(null, ATTR_MULTIPLE, -1);
+            if (count != -1) {
+                values.clear();
+                while (count > 0 && (type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+                    if (type == XmlPullParser.START_TAG
+                            && parser.getName().equals(TAG_VALUE)) {
+                        values.add(parser.nextText().trim());
+                        count--;
+                    }
+                }
+                String [] valueStrings = new String[values.size()];
+                values.toArray(valueStrings);
+                restrictions.putStringArray(key, valueStrings);
+            } else if (ATTR_TYPE_BUNDLE.equals(valType)) {
+                restrictions.putBundle(key, readBundleEntry(parser, values));
+            } else if (ATTR_TYPE_BUNDLE_ARRAY.equals(valType)) {
+                final int outerDepth = parser.getDepth();
+                ArrayList<Bundle> bundleList = new ArrayList<>();
+                while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+                    Bundle childBundle = readBundleEntry(parser, values);
+                    bundleList.add(childBundle);
+                }
+                restrictions.putParcelableArray(key,
+                        bundleList.toArray(new Bundle[bundleList.size()]));
+            } else {
+                String value = parser.nextText().trim();
+                if (ATTR_TYPE_BOOLEAN.equals(valType)) {
+                    restrictions.putBoolean(key, Boolean.parseBoolean(value));
+                } else if (ATTR_TYPE_INTEGER.equals(valType)) {
+                    restrictions.putInt(key, Integer.parseInt(value));
+                } else {
+                    restrictions.putString(key, value);
+                }
+            }
+        }
+    }
+
+    private static Bundle readBundleEntry(TypedXmlPullParser parser, ArrayList<String> values)
+            throws IOException, XmlPullParserException {
+        Bundle childBundle = new Bundle();
+        int outerDepth = parser.getDepth();
+        while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+            readEntry(childBundle, values, parser);
+        }
+        return childBundle;
+    }
+
+    private static void writeApplicationRestrictionsLAr(String fileName, Bundle restrictions) {
+        AtomicFile restrictionsFile = new AtomicFile(
+                new File(Environment.getDataSystemDirectory(), fileName));
+        writeApplicationRestrictionsLAr(restrictions, restrictionsFile);
+    }
+
+    static void writeApplicationRestrictionsLAr(Bundle restrictions, AtomicFile restrictionsFile) {
+        FileOutputStream fos = null;
+        try {
+            fos = restrictionsFile.startWrite();
+            final TypedXmlSerializer serializer = Xml.resolveSerializer(fos);
+            serializer.startDocument(null, true);
+            serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+
+            serializer.startTag(null, TAG_RESTRICTIONS);
+            writeBundle(restrictions, serializer);
+            serializer.endTag(null, TAG_RESTRICTIONS);
+
+            serializer.endDocument();
+            restrictionsFile.finishWrite(fos);
+        } catch (Exception e) {
+            restrictionsFile.failWrite(fos);
+            Slog.e(DevicePolicyEngine.TAG, "Error writing application restrictions list", e);
+        }
+    }
+
+    private static void writeBundle(Bundle restrictions, TypedXmlSerializer serializer)
+            throws IOException {
+        for (String key : restrictions.keySet()) {
+            Object value = restrictions.get(key);
+            serializer.startTag(null, TAG_ENTRY);
+            serializer.attribute(null, ATTR_KEY, key);
+
+            if (value instanceof Boolean) {
+                serializer.attribute(null, ATTR_VALUE_TYPE, ATTR_TYPE_BOOLEAN);
+                serializer.text(value.toString());
+            } else if (value instanceof Integer) {
+                serializer.attribute(null, ATTR_VALUE_TYPE, ATTR_TYPE_INTEGER);
+                serializer.text(value.toString());
+            } else if (value == null || value instanceof String) {
+                serializer.attribute(null, ATTR_VALUE_TYPE, ATTR_TYPE_STRING);
+                serializer.text(value != null ? (String) value : "");
+            } else if (value instanceof Bundle) {
+                serializer.attribute(null, ATTR_VALUE_TYPE, ATTR_TYPE_BUNDLE);
+                writeBundle((Bundle) value, serializer);
+            } else if (value instanceof Parcelable[]) {
+                serializer.attribute(null, ATTR_VALUE_TYPE, ATTR_TYPE_BUNDLE_ARRAY);
+                Parcelable[] array = (Parcelable[]) value;
+                for (Parcelable parcelable : array) {
+                    if (!(parcelable instanceof Bundle)) {
+                        throw new IllegalArgumentException("bundle-array can only hold Bundles");
+                    }
+                    serializer.startTag(null, TAG_ENTRY);
+                    serializer.attribute(null, ATTR_VALUE_TYPE, ATTR_TYPE_BUNDLE);
+                    writeBundle((Bundle) parcelable, serializer);
+                    serializer.endTag(null, TAG_ENTRY);
+                }
+            } else {
+                serializer.attribute(null, ATTR_VALUE_TYPE, ATTR_TYPE_STRING_ARRAY);
+                String[] values = (String[]) value;
+                serializer.attributeInt(null, ATTR_MULTIPLE, values.length);
+                for (String choice : values) {
+                    serializer.startTag(null, TAG_VALUE);
+                    serializer.text(choice != null ? choice : "");
+                    serializer.endTag(null, TAG_VALUE);
+                }
+            }
+            serializer.endTag(null, TAG_ENTRY);
+        }
+    }
+}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/ComponentNamePolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/ComponentNamePolicySerializer.java
index d400000..d1c6bcb 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/ComponentNamePolicySerializer.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/ComponentNamePolicySerializer.java
@@ -18,6 +18,8 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.admin.ComponentNamePolicyValue;
+import android.app.admin.PolicyKey;
 import android.content.ComponentName;
 import android.util.Log;
 
@@ -32,9 +34,8 @@
     private static final String ATTR_CLASS_NAME = ":class-name";
 
     @Override
-    void saveToXml(
-            TypedXmlSerializer serializer, String attributeNamePrefix, @NonNull ComponentName value)
-            throws IOException {
+    void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer, String attributeNamePrefix,
+            @NonNull ComponentName value) throws IOException {
         Objects.requireNonNull(value);
         serializer.attribute(
                 /* namespace= */ null,
@@ -46,7 +47,7 @@
 
     @Nullable
     @Override
-    ComponentName readFromXml(TypedXmlPullParser parser, String attributeNamePrefix) {
+    ComponentNamePolicyValue readFromXml(TypedXmlPullParser parser, String attributeNamePrefix) {
         String packageName = parser.getAttributeValue(
                 /* namespace= */ null, attributeNamePrefix + ATTR_PACKAGE_NAME);
         String className = parser.getAttributeValue(
@@ -55,6 +56,6 @@
             Log.e(DevicePolicyEngine.TAG, "Error parsing ComponentName policy.");
             return null;
         }
-        return new ComponentName(packageName, className);
+        return new ComponentNamePolicyValue(new ComponentName(packageName, className));
     }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DefaultPolicyKey.java b/services/devicepolicy/java/com/android/server/devicepolicy/DefaultPolicyKey.java
deleted file mode 100644
index ab0fc99..0000000
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DefaultPolicyKey.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * 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.server.devicepolicy;
-
-import com.android.modules.utils.TypedXmlPullParser;
-
-/**
- * Default implementation for {@link PolicyKey} used to identify a policy that doesn't require any
- * additional arguments to be represented in the policy engine's data structure.
- */
-final class DefaultPolicyKey extends PolicyKey {
-    private static final String ATTR_GENERIC_POLICY_KEY = "generic-policy-key";
-
-    DefaultPolicyKey(String policyKey) {
-        super(policyKey);
-    }
-
-    String getKey() {
-        return mKey;
-    }
-
-    static DefaultPolicyKey readGenericPolicyKeyFromXml(TypedXmlPullParser parser) {
-        String genericPolicyKey = parser.getAttributeValue(
-                /* namespace= */ null, ATTR_GENERIC_POLICY_KEY);
-        return new DefaultPolicyKey(genericPolicyKey);
-    }
-
-}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index cb3b021..71764dc 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -17,26 +17,34 @@
 package com.android.server.devicepolicy;
 
 import static android.app.admin.PolicyUpdateResult.RESULT_FAILURE_CONFLICTING_ADMIN_POLICY;
+import static android.app.admin.PolicyUpdateResult.RESULT_POLICY_CLEARED;
 import static android.app.admin.PolicyUpdateResult.RESULT_SUCCESS;
 import static android.app.admin.PolicyUpdatesReceiver.EXTRA_POLICY_TARGET_USER_ID;
 import static android.app.admin.PolicyUpdatesReceiver.EXTRA_POLICY_UPDATE_RESULT_KEY;
+import static android.content.pm.UserProperties.INHERIT_DEVICE_POLICY_FROM_PARENT;
+import static android.provider.DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER;
 
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.admin.DevicePolicyManager;
+import android.app.admin.DevicePolicyState;
+import android.app.admin.PolicyKey;
 import android.app.admin.PolicyUpdatesReceiver;
+import android.app.admin.PolicyValue;
 import android.app.admin.TargetUser;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.UserInfo;
+import android.content.pm.UserProperties;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Environment;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.provider.DeviceConfig;
 import android.util.AtomicFile;
 import android.util.Log;
 import android.util.SparseArray;
@@ -57,6 +65,7 @@
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -69,6 +78,9 @@
 final class DevicePolicyEngine {
     static final String TAG = "DevicePolicyEngine";
 
+    private static final String ENABLE_COEXISTENCE_FLAG = "enable_coexistence";
+    private static final boolean DEFAULT_ENABLE_COEXISTENCE_FLAG = true;
+
     private final Context mContext;
     private final UserManager mUserManager;
 
@@ -103,17 +115,20 @@
         mEnforcingAdmins = new SparseArray<>();
     }
 
-    // TODO: add more documentation on broadcasts/callbacks to use to get current enforced values
     /**
-     * Set the policy for the provided {@code policyDefinition}
-     * (see {@link PolicyDefinition}) and {@code enforcingAdmin} to the provided {@code value}.
+     * Set the policy for the provided {@code policyDefinition} (see {@link PolicyDefinition}) and
+     * {@code enforcingAdmin} to the provided {@code value}.
+     *
+     * <p>If {@code skipEnforcePolicy} is true, it sets the policies in the internal data structure
+     * but doesn't call the enforcing logic.
+     *
      */
     <V> void setLocalPolicy(
             @NonNull PolicyDefinition<V> policyDefinition,
             @NonNull EnforcingAdmin enforcingAdmin,
-            @NonNull V value,
-            int userId) {
-
+            @NonNull PolicyValue<V> value,
+            int userId,
+            boolean skipEnforcePolicy) {
         Objects.requireNonNull(policyDefinition);
         Objects.requireNonNull(enforcingAdmin);
         Objects.requireNonNull(value);
@@ -121,6 +136,12 @@
         synchronized (mLock) {
             PolicyState<V> localPolicyState = getLocalPolicyStateLocked(policyDefinition, userId);
 
+            if (policyDefinition.isNonCoexistablePolicy()) {
+                setNonCoexistableLocalPolicy(policyDefinition, localPolicyState, enforcingAdmin,
+                        value, userId, skipEnforcePolicy);
+                return;
+            }
+
             boolean hasGlobalPolicies = hasGlobalPolicyLocked(policyDefinition);
             boolean policyChanged;
             if (hasGlobalPolicies) {
@@ -133,25 +154,73 @@
                 policyChanged = localPolicyState.addPolicy(enforcingAdmin, value);
             }
 
-            if (policyChanged) {
-                onLocalPolicyChanged(policyDefinition, enforcingAdmin, userId);
+            // No need to notify admins as no new policy is actually enforced, we're just filling in
+            // the data structures.
+            if (!skipEnforcePolicy) {
+                if (policyChanged) {
+                    onLocalPolicyChanged(policyDefinition, enforcingAdmin, userId);
+                }
+                boolean policyEnforced = Objects.equals(
+                        localPolicyState.getCurrentResolvedPolicy(), value);
+                sendPolicyResultToAdmin(
+                        enforcingAdmin,
+                        policyDefinition,
+                        // TODO: we're always sending this for now, should properly handle errors.
+                        policyEnforced ? RESULT_SUCCESS : RESULT_FAILURE_CONFLICTING_ADMIN_POLICY,
+                        userId);
             }
 
-            boolean policyEnforced = Objects.equals(
-                    localPolicyState.getCurrentResolvedPolicy(), value);
-            sendPolicyResultToAdmin(
-                    enforcingAdmin,
-                    policyDefinition,
-                    // TODO: we're always sending this for now, should properly handle errors.
-                    policyEnforced ? RESULT_SUCCESS : RESULT_FAILURE_CONFLICTING_ADMIN_POLICY,
-                    userId);
-
             updateDeviceAdminServiceOnPolicyAddLocked(enforcingAdmin);
 
             write();
+
+            applyToInheritableProfiles(policyDefinition, enforcingAdmin, value, userId);
         }
     }
 
+    /**
+     * Sets a non-coexistable policy, meaning it doesn't get resolved against other policies set
+     * by other admins, and no callbacks are sent to admins, this is just storing and
+     * enforcing the policy.
+     *
+     * <p>Passing a {@code null} value means the policy set by this admin should be removed.
+     */
+    private <V> void setNonCoexistableLocalPolicy(
+            PolicyDefinition<V> policyDefinition,
+            PolicyState<V> localPolicyState,
+            EnforcingAdmin enforcingAdmin,
+            @Nullable PolicyValue<V> value,
+            int userId,
+            boolean skipEnforcePolicy) {
+        if (value == null) {
+            localPolicyState.removePolicy(enforcingAdmin);
+        } else {
+            localPolicyState.addPolicy(enforcingAdmin, value);
+        }
+        if (!skipEnforcePolicy) {
+            enforcePolicy(policyDefinition, value, userId);
+        }
+        if (localPolicyState.getPoliciesSetByAdmins().isEmpty()) {
+            removeLocalPolicyStateLocked(policyDefinition, userId);
+        }
+        updateDeviceAdminServiceOnPolicyAddLocked(enforcingAdmin);
+        write();
+    }
+
+    // TODO: add more documentation on broadcasts/callbacks to use to get current enforced values
+    /**
+     * Set the policy for the provided {@code policyDefinition}
+     * (see {@link PolicyDefinition}) and {@code enforcingAdmin} to the provided {@code value}.
+     */
+    <V> void setLocalPolicy(
+            @NonNull PolicyDefinition<V> policyDefinition,
+            @NonNull EnforcingAdmin enforcingAdmin,
+            @NonNull PolicyValue<V> value,
+            int userId) {
+        setLocalPolicy(
+                policyDefinition, enforcingAdmin, value, userId, /* skipEnforcePolicy= */ false);
+    }
+
     // TODO: add more documentation on broadcasts/callbacks to use to get current enforced values
     /**
      * Removes any previously set policy for the provided {@code policyDefinition}
@@ -170,6 +239,12 @@
             }
             PolicyState<V> localPolicyState = getLocalPolicyStateLocked(policyDefinition, userId);
 
+            if (policyDefinition.isNonCoexistablePolicy()) {
+                setNonCoexistableLocalPolicy(policyDefinition, localPolicyState, enforcingAdmin,
+                        /* value= */ null, userId, /* skipEnforcePolicy= */ false);
+                return;
+            }
+
             boolean policyChanged;
             if (hasGlobalPolicyLocked(policyDefinition)) {
                 PolicyState<V> globalPolicyState = getGlobalPolicyStateLocked(policyDefinition);
@@ -185,12 +260,11 @@
             }
 
             // For a removePolicy to be enforced, it means no current policy exists
-            boolean policyEnforced = localPolicyState.getCurrentResolvedPolicy() == null;
             sendPolicyResultToAdmin(
                     enforcingAdmin,
                     policyDefinition,
                     // TODO: we're always sending this for now, should properly handle errors.
-                    policyEnforced ? RESULT_SUCCESS : RESULT_FAILURE_CONFLICTING_ADMIN_POLICY,
+                    RESULT_POLICY_CLEARED,
                     userId);
 
             if (localPolicyState.getPoliciesSetByAdmins().isEmpty()) {
@@ -200,10 +274,52 @@
             updateDeviceAdminServiceOnPolicyRemoveLocked(enforcingAdmin);
 
             write();
+
+            applyToInheritableProfiles(policyDefinition, enforcingAdmin, /*value */ null, userId);
         }
     }
 
     /**
+     * If any of child user has property {@link UserProperties#INHERIT_DEVICE_POLICY_FROM_PARENT}
+     * set then propagate the policy to it if value is not null
+     * else remove the policy from child.
+     */
+    private <V> void applyToInheritableProfiles(PolicyDefinition<V> policyDefinition,
+            EnforcingAdmin enforcingAdmin, PolicyValue<V> value, int userId) {
+        if (policyDefinition.isInheritable()) {
+            Binder.withCleanCallingIdentity(() -> {
+                List<UserInfo> userInfos = mUserManager.getProfiles(userId);
+                for (UserInfo childUserInfo : userInfos) {
+                    int childUserId = childUserInfo.getUserHandle().getIdentifier();
+                    if (isProfileOfUser(childUserId, userId)
+                            && isInheritDevicePolicyFromParent(childUserInfo)) {
+                        if (value != null) {
+                            setLocalPolicy(policyDefinition, enforcingAdmin, value, childUserId);
+                        } else {
+                            removeLocalPolicy(policyDefinition, enforcingAdmin, childUserId);
+                        }
+                    }
+                }
+            });
+        }
+    }
+
+    /**
+     * Checks if given parentUserId is direct parent of childUserId.
+     */
+    private boolean isProfileOfUser(int childUserId, int parentUserId) {
+        UserInfo parentInfo = mUserManager.getProfileParent(childUserId);
+        return childUserId != parentUserId && parentInfo != null
+                && parentInfo.getUserHandle().getIdentifier() == parentUserId;
+    }
+
+    private boolean isInheritDevicePolicyFromParent(UserInfo userInfo) {
+        UserProperties userProperties = mUserManager.getUserProperties(userInfo.getUserHandle());
+        return userProperties != null && mUserManager.getUserProperties(userInfo.getUserHandle())
+                .getInheritDevicePolicy() == INHERIT_DEVICE_POLICY_FROM_PARENT;
+    }
+
+    /**
      * Enforces the new policy and notifies relevant admins.
      */
     private <V> void onLocalPolicyChanged(
@@ -234,6 +350,18 @@
                     userId);
         }
     }
+
+    /**
+     * Set the policy for the provided {@code policyDefinition}
+     * (see {@link PolicyDefinition}) and {@code enforcingAdmin} to the provided {@code value}.
+     */
+    <V> void setGlobalPolicy(
+            @NonNull PolicyDefinition<V> policyDefinition,
+            @NonNull EnforcingAdmin enforcingAdmin,
+            @NonNull PolicyValue<V> value) {
+        setGlobalPolicy(policyDefinition, enforcingAdmin, value, /* skipEnforcePolicy= */ false);
+    }
+
     // TODO: add more documentation on broadcasts/callbacks to use to get current enforced values
     /**
      * Set the policy for the provided {@code policyDefinition}
@@ -242,7 +370,8 @@
     <V> void setGlobalPolicy(
             @NonNull PolicyDefinition<V> policyDefinition,
             @NonNull EnforcingAdmin enforcingAdmin,
-            @NonNull V value) {
+            @NonNull PolicyValue<V> value,
+            boolean skipEnforcePolicy) {
 
         Objects.requireNonNull(policyDefinition);
         Objects.requireNonNull(enforcingAdmin);
@@ -252,23 +381,28 @@
             PolicyState<V> globalPolicyState = getGlobalPolicyStateLocked(policyDefinition);
 
             boolean policyChanged = globalPolicyState.addPolicy(enforcingAdmin, value);
-            if (policyChanged) {
-                onGlobalPolicyChanged(policyDefinition, enforcingAdmin);
+            boolean policyAppliedOnAllUsers = applyGlobalPolicyOnUsersWithLocalPoliciesLocked(
+                    policyDefinition, enforcingAdmin, value, skipEnforcePolicy);
+
+            // No need to notify admins as no new policy is actually enforced, we're just filling in
+            // the data structures.
+            if (!skipEnforcePolicy) {
+                if (policyChanged) {
+                    onGlobalPolicyChanged(policyDefinition, enforcingAdmin);
+                }
+
+                boolean policyAppliedGlobally = Objects.equals(
+                        globalPolicyState.getCurrentResolvedPolicy(), value);
+                boolean policyApplied = policyAppliedGlobally && policyAppliedOnAllUsers;
+
+                sendPolicyResultToAdmin(
+                        enforcingAdmin,
+                        policyDefinition,
+                        // TODO: we're always sending this for now, should properly handle errors.
+                        policyApplied ? RESULT_SUCCESS : RESULT_FAILURE_CONFLICTING_ADMIN_POLICY,
+                        UserHandle.USER_ALL);
             }
 
-            boolean policyEnforcedOnAllUsers = enforceGlobalPolicyOnUsersWithLocalPoliciesLocked(
-                    policyDefinition, enforcingAdmin, value);
-            boolean policyEnforcedGlobally = Objects.equals(
-                    globalPolicyState.getCurrentResolvedPolicy(), value);
-            boolean policyEnforced = policyEnforcedGlobally && policyEnforcedOnAllUsers;
-
-            sendPolicyResultToAdmin(
-                    enforcingAdmin,
-                    policyDefinition,
-                    // TODO: we're always sending this for now, should properly handle errors.
-                    policyEnforced ? RESULT_SUCCESS : RESULT_FAILURE_CONFLICTING_ADMIN_POLICY,
-                    UserHandle.USER_ALL);
-
             updateDeviceAdminServiceOnPolicyAddLocked(enforcingAdmin);
 
             write();
@@ -295,17 +429,14 @@
                 onGlobalPolicyChanged(policyDefinition, enforcingAdmin);
             }
 
-            boolean policyEnforcedOnAllUsers = enforceGlobalPolicyOnUsersWithLocalPoliciesLocked(
-                    policyDefinition, enforcingAdmin, /* value= */ null);
-            // For a removePolicy to be enforced, it means no current policy exists
-            boolean policyEnforcedGlobally = policyState.getCurrentResolvedPolicy() == null;
-            boolean policyEnforced = policyEnforcedGlobally && policyEnforcedOnAllUsers;
+            applyGlobalPolicyOnUsersWithLocalPoliciesLocked(
+                    policyDefinition, enforcingAdmin, /* value= */ null, /* enforcePolicy= */ true);
 
             sendPolicyResultToAdmin(
                     enforcingAdmin,
                     policyDefinition,
                     // TODO: we're always sending this for now, should properly handle errors.
-                    policyEnforced ? RESULT_SUCCESS : RESULT_FAILURE_CONFLICTING_ADMIN_POLICY,
+                    RESULT_POLICY_CLEARED,
                     UserHandle.USER_ALL);
 
             if (policyState.getPoliciesSetByAdmins().isEmpty()) {
@@ -346,15 +477,16 @@
      *
      * <p>Returns {@code true} if the policy is enforced successfully on all users.
      */
-    private <V> boolean enforceGlobalPolicyOnUsersWithLocalPoliciesLocked(
+    private <V> boolean applyGlobalPolicyOnUsersWithLocalPoliciesLocked(
             @NonNull PolicyDefinition<V> policyDefinition,
             @NonNull EnforcingAdmin enforcingAdmin,
-            @Nullable V value) {
+            @Nullable PolicyValue<V> value,
+            boolean skipEnforcePolicy) {
         // Global only policies can't be applied locally, return early.
         if (policyDefinition.isGlobalOnlyPolicy()) {
             return true;
         }
-        boolean isAdminPolicyEnforced = true;
+        boolean isAdminPolicyApplied = true;
         for (int i = 0; i < mLocalPolicies.size(); i++) {
             int userId = mLocalPolicies.keyAt(i);
             if (!hasLocalPolicyLocked(policyDefinition, userId)) {
@@ -366,9 +498,11 @@
 
             boolean policyChanged = localPolicyState.resolvePolicy(
                     globalPolicyState.getPoliciesSetByAdmins());
-            if (policyChanged) {
+            if (policyChanged && !skipEnforcePolicy) {
                 enforcePolicy(
-                        policyDefinition, localPolicyState.getCurrentResolvedPolicy(), userId);
+                        policyDefinition,
+                        localPolicyState.getCurrentResolvedPolicy(),
+                        userId);
                 sendPolicyChangedToAdmins(
                         localPolicyState,
                         enforcingAdmin,
@@ -378,10 +512,10 @@
                         userId);
 
             }
-            isAdminPolicyEnforced &= Objects.equals(
+            isAdminPolicyApplied &= Objects.equals(
                     value, localPolicyState.getCurrentResolvedPolicy());
         }
-        return isAdminPolicyEnforced;
+        return isAdminPolicyApplied;
     }
 
     /**
@@ -392,14 +526,16 @@
         Objects.requireNonNull(policyDefinition);
 
         synchronized (mLock) {
+            PolicyValue<V> resolvedValue = null;
             if (hasLocalPolicyLocked(policyDefinition, userId)) {
-                return getLocalPolicyStateLocked(
+                resolvedValue = getLocalPolicyStateLocked(
                         policyDefinition, userId).getCurrentResolvedPolicy();
             }
             if (hasGlobalPolicyLocked(policyDefinition)) {
-                return getGlobalPolicyStateLocked(policyDefinition).getCurrentResolvedPolicy();
+                resolvedValue = getGlobalPolicyStateLocked(
+                        policyDefinition).getCurrentResolvedPolicy();
             }
-            return null;
+            return resolvedValue == null ? null : resolvedValue.getValue();
         }
     }
 
@@ -420,13 +556,30 @@
                 return null;
             }
             return getLocalPolicyStateLocked(policyDefinition, userId)
-                    .getPoliciesSetByAdmins().get(enforcingAdmin);
+                    .getPoliciesSetByAdmins().get(enforcingAdmin).getValue();
         }
     }
 
     /**
-     * Returns the policies set by the given admin that share the same {@link PolicyKey#getKey()} as
-     * the provided {@code policyDefinition}.
+     * Retrieves the values set for the provided {@code policyDefinition} by each admin.
+     */
+    @NonNull
+    <V> LinkedHashMap<EnforcingAdmin, PolicyValue<V>> getLocalPoliciesSetByAdmins(
+            @NonNull PolicyDefinition<V> policyDefinition,
+            int userId) {
+        Objects.requireNonNull(policyDefinition);
+
+        synchronized (mLock) {
+            if (!hasLocalPolicyLocked(policyDefinition, userId)) {
+                return new LinkedHashMap<>();
+            }
+            return getLocalPolicyStateLocked(policyDefinition, userId).getPoliciesSetByAdmins();
+        }
+    }
+
+    /**
+     * Returns the policies set by the given admin that share the same
+     * {@link PolicyKey#getIdentifier()} as the provided {@code policyDefinition}.
      *
      * <p>For example, getLocalPolicyKeysSetByAdmin(PERMISSION_GRANT, admin) returns all permission
      * grants set by the given admin.
@@ -450,7 +603,7 @@
             }
             Set<PolicyKey> keys = new HashSet<>();
             for (PolicyKey key : mLocalPolicies.get(userId).keySet()) {
-                if (key.hasSameKeyAs(policyDefinition.getPolicyKey())
+                if (key.hasSameIdentifierAs(policyDefinition.getPolicyKey())
                         && mLocalPolicies.get(userId).get(key).getPoliciesSetByAdmins()
                         .containsKey(enforcingAdmin)) {
                     keys.add(key);
@@ -545,11 +698,12 @@
         }
     }
 
-    private <V> void enforcePolicy(
-            PolicyDefinition<V> policyDefinition, @Nullable V policyValue, int userId) {
+    private <V> void enforcePolicy(PolicyDefinition<V> policyDefinition,
+            @Nullable PolicyValue<V> policyValue, int userId) {
         // null policyValue means remove any enforced policies, ensure callbacks handle this
         // properly
-        policyDefinition.enforcePolicy(policyValue, mContext, userId);
+        policyDefinition.enforcePolicy(
+                policyValue == null ? null : policyValue.getValue(), mContext, userId);
     }
 
     private <V> void sendPolicyResultToAdmin(
@@ -688,6 +842,47 @@
         }
     }
 
+    void handleUserCreated(UserInfo user) {
+        enforcePoliciesOnInheritableProfilesIfApplicable(user);
+    }
+
+    private void enforcePoliciesOnInheritableProfilesIfApplicable(UserInfo user) {
+        if (!user.isProfile()) {
+            return;
+        }
+
+        Binder.withCleanCallingIdentity(() -> {
+            UserProperties userProperties = mUserManager.getUserProperties(user.getUserHandle());
+            if (userProperties == null || userProperties.getInheritDevicePolicy()
+                    != INHERIT_DEVICE_POLICY_FROM_PARENT) {
+                return;
+            }
+
+            int userId = user.id;
+            // Apply local policies present on parent to newly created child profile.
+            UserInfo parentInfo = mUserManager.getProfileParent(userId);
+            if (parentInfo == null || parentInfo.getUserHandle().getIdentifier() == userId) return;
+
+            for (Map.Entry<PolicyKey, PolicyState<?>> entry : mLocalPolicies.get(
+                    parentInfo.getUserHandle().getIdentifier()).entrySet()) {
+                enforcePolicyOnUser(userId, entry.getValue());
+            }
+        });
+    }
+
+    private <V> void enforcePolicyOnUser(int userId, PolicyState<V> policyState) {
+        if (!policyState.getPolicyDefinition().isInheritable()) {
+            return;
+        }
+        for (Map.Entry<EnforcingAdmin, PolicyValue<V>> enforcingAdminEntry :
+                policyState.getPoliciesSetByAdmins().entrySet()) {
+            setLocalPolicy(policyState.getPolicyDefinition(),
+                    enforcingAdminEntry.getKey(),
+                    enforcingAdminEntry.getValue(),
+                    userId);
+        }
+    }
+
     /**
      * Handles internal state related to a user getting started.
      */
@@ -723,6 +918,34 @@
     }
 
     /**
+     * Returns all current enforced policies set on the device, and the individual values set by
+     * each admin. Global policies are returned under {@link UserHandle#ALL}.
+     */
+    @NonNull
+    DevicePolicyState getDevicePolicyState() {
+        Map<UserHandle, Map<PolicyKey, android.app.admin.PolicyState<?>>> policies =
+                new HashMap<>();
+        for (int i = 0; i < mLocalPolicies.size(); i++) {
+            UserHandle user = UserHandle.of(mLocalPolicies.keyAt(i));
+            policies.put(user, new HashMap<>());
+            for (PolicyKey policyKey : mLocalPolicies.valueAt(i).keySet()) {
+                policies.get(user).put(
+                        policyKey,
+                        mLocalPolicies.valueAt(i).get(policyKey).getParcelablePolicyState());
+            }
+        }
+        if (!mGlobalPolicies.isEmpty()) {
+            policies.put(UserHandle.ALL, new HashMap<>());
+            for (PolicyKey policyKey : mGlobalPolicies.keySet()) {
+                policies.get(UserHandle.ALL).put(
+                        policyKey,
+                        mGlobalPolicies.get(policyKey).getParcelablePolicyState());
+            }
+        }
+        return new DevicePolicyState(policies);
+    }
+
+    /**
      * Reestablishes the service that handles
      * {@link DevicePolicyManager#ACTION_DEVICE_ADMIN_SERVICE} in the enforcing admin if the package
      * was updated, as a package update results in the persistent connection getting reset.
@@ -748,11 +971,6 @@
     private void updateDeviceAdminServiceOnPolicyAddLocked(@NonNull EnforcingAdmin enforcingAdmin) {
         int userId = enforcingAdmin.getUserId();
 
-        // A connection is established with DPCs as soon as they are provisioned, so no need to
-        // connect when a policy is set.
-        if (enforcingAdmin.hasAuthority(EnforcingAdmin.DPC_AUTHORITY)) {
-            return;
-        }
         if (mEnforcingAdmins.contains(userId)
                 && mEnforcingAdmins.get(userId).contains(enforcingAdmin)) {
             return;
@@ -763,6 +981,11 @@
         }
         mEnforcingAdmins.get(enforcingAdmin.getUserId()).add(enforcingAdmin);
 
+        // A connection is established with DPCs as soon as they are provisioned, so no need to
+        // connect when a policy is set.
+        if (enforcingAdmin.hasAuthority(EnforcingAdmin.DPC_AUTHORITY)) {
+            return;
+        }
         mDeviceAdminServiceController.startServiceForAdmin(
                 enforcingAdmin.getPackageName(),
                 userId,
@@ -775,18 +998,10 @@
      */
     private void updateDeviceAdminServiceOnPolicyRemoveLocked(
             @NonNull EnforcingAdmin enforcingAdmin) {
-        // TODO(b/263364434): centralise handling in one place.
-        // DPCs rely on a constant connection being established as soon as they are provisioned,
-        // so we shouldn't disconnect it even if they no longer have policies set.
-        if (enforcingAdmin.hasAuthority(EnforcingAdmin.DPC_AUTHORITY)) {
-            return;
-        }
         if (doesAdminHavePolicies(enforcingAdmin)) {
             return;
         }
-
         int userId = enforcingAdmin.getUserId();
-
         if (mEnforcingAdmins.contains(userId)) {
             mEnforcingAdmins.get(userId).remove(enforcingAdmin);
             if (mEnforcingAdmins.get(userId).isEmpty()) {
@@ -794,6 +1009,12 @@
             }
         }
 
+        // TODO(b/263364434): centralise handling in one place.
+        // DPCs rely on a constant connection being established as soon as they are provisioned,
+        // so we shouldn't disconnect it even if they no longer have policies set.
+        if (enforcingAdmin.hasAuthority(EnforcingAdmin.DPC_AUTHORITY)) {
+            return;
+        }
         mDeviceAdminServiceController.stopServiceForAdmin(
                 enforcingAdmin.getPackageName(),
                 userId,
@@ -840,21 +1061,70 @@
         }
     }
 
+    /**
+     * Clear all policies set in the policy engine.
+     *
+     * <p>Note that this doesn't clear any enforcements, it only clears the data structures.
+     */
+    void clearAllPolicies() {
+        synchronized (mLock) {
+            clear();
+            write();
+        }
+    }
     private void clear() {
         synchronized (mLock) {
             mGlobalPolicies.clear();
             mLocalPolicies.clear();
+            mEnforcingAdmins.clear();
         }
     }
 
+    // TODO: we need to listen for user removal and package removal and update out internal policy
+    //  map and enforcing admins for this is be accurate.
+    boolean hasActivePolicies() {
+        return mEnforcingAdmins.size() > 0;
+    }
+
+    /**
+     * Returns {@code true} if the coexistence flag is enabled or:
+     * <ul>
+     * <li>If the provided package is an admin with existing policies
+     * <li>A new admin and no other admin have policies set
+     * <li>More than one admin have policies set
+     */
+    boolean canAdminAddPolicies(String packageName, int userId) {
+        if (isCoexistenceFlagEnabled()) {
+            return true;
+        }
+
+        if (mEnforcingAdmins.contains(userId)
+                && mEnforcingAdmins.get(userId).stream().anyMatch(admin ->
+                admin.getPackageName().equals(packageName))) {
+            return true;
+        }
+
+        int numOfEnforcingAdmins = 0;
+        for (int i = 0; i < mEnforcingAdmins.size(); i++) {
+            numOfEnforcingAdmins += mEnforcingAdmins.get(i).size();
+        }
+        return numOfEnforcingAdmins == 0 || numOfEnforcingAdmins > 1;
+    }
+
+    private boolean isCoexistenceFlagEnabled() {
+        return DeviceConfig.getBoolean(
+                NAMESPACE_DEVICE_POLICY_MANAGER,
+                ENABLE_COEXISTENCE_FLAG,
+                DEFAULT_ENABLE_COEXISTENCE_FLAG);
+    }
+
     private class DevicePoliciesReaderWriter {
-        private static final String DEVICE_POLICIES_XML = "device_policies.xml";
+        private static final String DEVICE_POLICIES_XML = "device_policy_state.xml";
         private static final String TAG_LOCAL_POLICY_ENTRY = "local-policy-entry";
         private static final String TAG_GLOBAL_POLICY_ENTRY = "global-policy-entry";
         private static final String TAG_ADMINS_POLICY_ENTRY = "admins-policy-entry";
         private static final String TAG_ENFORCING_ADMINS_ENTRY = "enforcing-admins-entry";
         private static final String ATTR_USER_ID = "user-id";
-        private static final String ATTR_POLICY_ID = "policy-id";
 
         private final File mFile;
 
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 5dafda4..f5a1b80 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -21,6 +21,7 @@
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_ACROSS_USERS;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL;
+import static android.Manifest.permission.MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY;
 import static android.Manifest.permission.QUERY_ADMIN_POLICY;
 import static android.Manifest.permission.REQUEST_PASSWORD_COMPLEXITY;
 import static android.Manifest.permission.SET_TIME;
@@ -29,6 +30,7 @@
 import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
 import static android.app.AppOpsManager.MODE_ALLOWED;
 import static android.app.AppOpsManager.MODE_DEFAULT;
+import static android.app.AppOpsManager.OPSTR_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION;
 import static android.app.AppOpsManager.OPSTR_SYSTEM_EXEMPT_FROM_APP_STANDBY;
 import static android.app.AppOpsManager.OPSTR_SYSTEM_EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS;
 import static android.app.admin.DeviceAdminInfo.HEADLESS_DEVICE_OWNER_MODE_AFFILIATED;
@@ -41,6 +43,7 @@
 import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE;
 import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_USER;
 import static android.app.admin.DevicePolicyManager.ACTION_SYSTEM_UPDATE_POLICY_CHANGED;
+import static android.app.admin.DevicePolicyManager.AUTO_TIMEZONE_POLICY;
 import static android.app.admin.DevicePolicyManager.DELEGATION_APP_RESTRICTIONS;
 import static android.app.admin.DevicePolicyManager.DELEGATION_BLOCK_UNINSTALL;
 import static android.app.admin.DevicePolicyManager.DELEGATION_CERT_INSTALL;
@@ -55,6 +58,7 @@
 import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_DEFAULT;
 import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED;
 import static android.app.admin.DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_PER_USER;
+import static android.app.admin.DevicePolicyManager.EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION;
 import static android.app.admin.DevicePolicyManager.EXEMPT_FROM_APP_STANDBY;
 import static android.app.admin.DevicePolicyManager.EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS;
 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE;
@@ -205,6 +209,9 @@
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.app.StatusBarManager;
+import android.app.admin.BooleanPolicyValue;
+import android.app.admin.BundlePolicyValue;
+import android.app.admin.ComponentNamePolicyValue;
 import android.app.admin.DeviceAdminInfo;
 import android.app.admin.DeviceAdminReceiver;
 import android.app.admin.DevicePolicyCache;
@@ -219,11 +226,15 @@
 import android.app.admin.DevicePolicyManagerInternal;
 import android.app.admin.DevicePolicyManagerLiteInternal;
 import android.app.admin.DevicePolicySafetyChecker;
+import android.app.admin.DevicePolicyState;
 import android.app.admin.DevicePolicyStringResource;
 import android.app.admin.DeviceStateCache;
 import android.app.admin.FactoryResetProtectionPolicy;
 import android.app.admin.FullyManagedDeviceProvisioningParams;
 import android.app.admin.IDevicePolicyManager;
+import android.app.admin.IntegerPolicyValue;
+import android.app.admin.IntentFilterPolicyKey;
+import android.app.admin.LockTaskPolicy;
 import android.app.admin.ManagedProfileProvisioningParams;
 import android.app.admin.ManagedSubscriptionsPolicy;
 import android.app.admin.NetworkEvent;
@@ -232,10 +243,13 @@
 import android.app.admin.ParcelableResource;
 import android.app.admin.PasswordMetrics;
 import android.app.admin.PasswordPolicy;
+import android.app.admin.PolicyKey;
+import android.app.admin.PolicyValue;
 import android.app.admin.PreferentialNetworkServiceConfig;
 import android.app.admin.SecurityLog;
 import android.app.admin.SecurityLog.SecurityEvent;
 import android.app.admin.StartInstallingUpdateCallback;
+import android.app.admin.StringSetPolicyValue;
 import android.app.admin.SystemUpdateInfo;
 import android.app.admin.SystemUpdatePolicy;
 import android.app.admin.UnsafeStateException;
@@ -280,6 +294,7 @@
 import android.content.pm.StringParceledListSlice;
 import android.content.pm.UserInfo;
 import android.content.pm.UserPackage;
+import android.content.pm.parsing.FrameworkParsingPackageUtils;
 import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.database.Cursor;
@@ -342,7 +357,6 @@
 import android.security.keystore.KeyGenParameterSpec;
 import android.security.keystore.ParcelableKeyGenParameterSpec;
 import android.stats.devicepolicy.DevicePolicyEnums;
-import android.telecom.TelecomManager;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.telephony.data.ApnSetting;
@@ -433,6 +447,7 @@
 import java.util.Date;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
@@ -708,6 +723,9 @@
         APPLICATION_EXEMPTION_CONSTANTS_TO_APP_OPS.put(
                 EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS,
                 OPSTR_SYSTEM_EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS);
+        APPLICATION_EXEMPTION_CONSTANTS_TO_APP_OPS.put(
+                EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION,
+                OPSTR_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION);
     }
 
     /**
@@ -737,11 +755,18 @@
                     + "management app's authentication policy";
     private static final String NOT_SYSTEM_CALLER_MSG = "Only the system can %s";
 
+    // ENABLE_DEVICE_POLICY_ENGINE_FLAG must be enabled before this could be enabled.
     private static final String PERMISSION_BASED_ACCESS_EXPERIMENT_FLAG =
             "enable_permission_based_access";
-    private static final String ENABLE_COEXISTENCE_FLAG = "enable_coexistence";
     private static final boolean DEFAULT_VALUE_PERMISSION_BASED_ACCESS_FLAG = false;
-    private static final boolean DEFAULT_ENABLE_COEXISTENCE_FLAG = false;
+
+    // This must be enabled before PERMISSION_BASED_ACCESS_EXPERIMENT_FLAG is enabled, the reason
+    // we're not just relying on PERMISSION_BASED_ACCESS_EXPERIMENT_FLAG to enable the policy engine
+    // is that we might want to enable it before the permission changes are ready if we want to test
+    // it on DPCs.
+    // Once this is enabled, it can no longer be disabled in production
+    private static final String ENABLE_DEVICE_POLICY_ENGINE_FLAG = "enable_device_policy_engine";
+    private static final boolean DEFAULT_ENABLE_DEVICE_POLICY_ENGINE_FLAG = false;
 
     // TODO(b/258425381) remove the flag after rollout.
     private static final String KEEP_PROFILES_RUNNING_FLAG = "enable_keep_profiles_running";
@@ -1301,10 +1326,12 @@
                     && (owner.getPackageName().equals(packageName))) {
                 startOwnerService(userHandle, "package-broadcast");
             }
-            if (isCoexistenceFlagEnabled()) {
+            if (shouldMigrateToDevicePolicyEngine()) {
+                migratePoliciesToDevicePolicyEngine();
+            }
+            if (isDevicePolicyEngineFlagEnabled()) {
                 mDevicePolicyEngine.handlePackageChanged(packageName, userHandle);
             }
-
             // Persist updates if the removed package was an admin or delegate.
             if (removedAdmin || removedDelegate) {
                 saveSettingsLocked(policy.mUserId);
@@ -1994,7 +2021,7 @@
         mUserManagerInternal.addUserLifecycleListener(new UserLifecycleListener());
 
         mDeviceManagementResourcesProvider.load();
-        if (isCoexistenceFlagEnabled()) {
+        if (isDevicePolicyEngineFlagEnabled()) {
             mDevicePolicyEngine.load();
         }
 
@@ -3125,6 +3152,11 @@
                 synchronized (getLockObject()) {
                     migrateToProfileOnOrganizationOwnedDeviceIfCompLocked();
                     applyProfileRestrictionsIfDeviceOwnerLocked();
+
+                    // TODO: Is this the right place to trigger the migration?
+                    if (shouldMigrateToDevicePolicyEngine()) {
+                        migratePoliciesToDevicePolicyEngine();
+                    }
                 }
                 maybeStartSecurityLogMonitorOnActivityManagerReady();
                 break;
@@ -3333,7 +3365,7 @@
         updateNetworkPreferenceForUser(userId, preferentialNetworkServiceConfigs);
 
         startOwnerService(userId, "start-user");
-        if (isCoexistenceFlagEnabled()) {
+        if (isDevicePolicyEngineFlagEnabled()) {
             mDevicePolicyEngine.handleStartUser(userId);
         }
     }
@@ -3358,7 +3390,7 @@
 
     void handleUnlockUser(int userId) {
         startOwnerService(userId, "unlock-user");
-        if (isCoexistenceFlagEnabled()) {
+        if (isDevicePolicyEngineFlagEnabled()) {
             mDevicePolicyEngine.handleUnlockUser(userId);
         }
     }
@@ -3370,7 +3402,7 @@
     void handleStopUser(int userId) {
         updateNetworkPreferenceForUser(userId, List.of(PreferentialNetworkServiceConfig.DEFAULT));
         mDeviceAdminServiceController.stopServicesForUser(userId, /* actionForLog= */ "stop-user");
-        if (isCoexistenceFlagEnabled()) {
+        if (isDevicePolicyEngineFlagEnabled()) {
             mDevicePolicyEngine.handleStopUser(userId);
         }
     }
@@ -3478,7 +3510,8 @@
      * @param refreshing true = update an active admin, no error
      */
     @Override
-    public void setActiveAdmin(ComponentName adminReceiver, boolean refreshing, int userHandle) {
+    public void setActiveAdmin(
+            ComponentName adminReceiver, boolean refreshing, int userHandle) {
         if (!mHasFeature) {
             return;
         }
@@ -3495,6 +3528,11 @@
         synchronized (getLockObject()) {
             checkActiveAdminPrecondition(adminReceiver, info, policy);
             mInjector.binderWithCleanCallingIdentity(() -> {
+                if (!canAddActiveAdminIfPolicyEngineEnabled(
+                        adminReceiver.getPackageName(), userHandle)) {
+                    throw new IllegalStateException("Can't add non-coexistable admin.");
+                }
+
                 final ActiveAdmin existingAdmin
                         = getActiveAdminUncheckedLocked(adminReceiver, userHandle);
                 if (!refreshing && existingAdmin != null) {
@@ -8238,14 +8276,14 @@
                     caller));
         }
 
-        if (isCoexistenceEnabled(caller)) {
+        if (useDevicePolicyEngine(caller, /* delegateScope= */ null)) {
             mDevicePolicyEngine.setGlobalPolicy(
                     PolicyDefinition.AUTO_TIMEZONE,
                     // TODO(b/260573124): add correct enforcing admin when permission changes are
                     //  merged.
                     EnforcingAdmin.createEnterpriseEnforcingAdmin(
                             caller.getComponentName(), caller.getUserId()),
-                    enabled);
+                    new BooleanPolicyValue(enabled));
         } else {
             mInjector.binderWithCleanCallingIdentity(() ->
                     mInjector.settingsGlobalPutInt(Global.AUTO_TIME_ZONE, enabled ? 1 : 0));
@@ -10348,11 +10386,11 @@
                 || isDefaultDeviceOwner(caller) || isFinancedDeviceOwner(caller));
 
         final int userHandle = caller.getUserId();
-        if (isCoexistenceEnabled(caller)) {
+        if (useDevicePolicyEngine(caller, /* delegateScope= */ null)) {
             mDevicePolicyEngine.setLocalPolicy(
                     PolicyDefinition.PERSISTENT_PREFERRED_ACTIVITY(filter),
                     EnforcingAdmin.createEnterpriseEnforcingAdmin(who, userHandle),
-                    activity,
+                    new ComponentNamePolicyValue(activity),
                     userHandle);
         } else {
             synchronized (getLockObject()) {
@@ -10386,7 +10424,7 @@
 
         final int userHandle = caller.getUserId();
 
-        if (isCoexistenceEnabled(caller)) {
+        if (useDevicePolicyEngine(caller, /* delegateScope= */ null)) {
             clearPackagePersistentPreferredActivitiesFromPolicyEngine(
                     EnforcingAdmin.createEnterpriseEnforcingAdmin(who, userHandle),
                     packageName,
@@ -10422,13 +10460,13 @@
                 admin,
                 userId);
         for (PolicyKey key : keys) {
-            if (!(key instanceof PersistentPreferredActivityPolicyKey)) {
+            if (!(key instanceof IntentFilterPolicyKey)) {
                 throw new IllegalStateException("PolicyKey for PERSISTENT_PREFERRED_ACTIVITY is not"
                         + "of type PersistentPreferredActivityPolicyKey");
             }
-            PersistentPreferredActivityPolicyKey parsedKey =
-                    (PersistentPreferredActivityPolicyKey) key;
-            IntentFilter filter = Objects.requireNonNull(parsedKey.getFilter());
+            IntentFilterPolicyKey parsedKey =
+                    (IntentFilterPolicyKey) key;
+            IntentFilter filter = Objects.requireNonNull(parsedKey.getIntentFilter());
 
             ComponentName preferredActivity = mDevicePolicyEngine.getLocalPolicySetByAdmin(
                     PolicyDefinition.PERSISTENT_PREFERRED_ACTIVITY(filter),
@@ -10486,25 +10524,79 @@
 
     @Override
     public void setApplicationRestrictions(ComponentName who, String callerPackage,
-            String packageName, Bundle settings) {
+            String packageName, Bundle restrictions) {
         final CallerIdentity caller = getCallerIdentity(who, callerPackage);
         Preconditions.checkCallAuthorization((caller.hasAdminComponent()
                 && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
                 || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_APP_RESTRICTIONS)));
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_APPLICATION_RESTRICTIONS);
 
+        if (useDevicePolicyEngine(caller, DELEGATION_APP_RESTRICTIONS)) {
+            // This check is eventually made in UMS, checking here to fail early.
+            String validationResult =
+                    FrameworkParsingPackageUtils.validateName(packageName, false, false);
+            if (validationResult != null) {
+                throw new IllegalArgumentException("Invalid package name: " + validationResult);
+            }
+
+            EnforcingAdmin admin = EnforcingAdmin.createEnterpriseEnforcingAdmin(
+                    who == null
+                            ? new ComponentName(caller.getPackageName(), "Delegate")
+                            : who,
+                    caller.getUserId());
+
+            if (restrictions == null || restrictions.isEmpty()) {
+                mDevicePolicyEngine.removeLocalPolicy(
+                        PolicyDefinition.APPLICATION_RESTRICTIONS(packageName),
+                        admin,
+                        caller.getUserId());
+            } else {
+                mDevicePolicyEngine.setLocalPolicy(
+                        PolicyDefinition.APPLICATION_RESTRICTIONS(packageName),
+                        admin,
+                        new BundlePolicyValue(restrictions),
+                        caller.getUserId());
+            }
+            setBackwardsCompatibleAppRestrictions(
+                    packageName, restrictions, caller.getUserHandle());
+        } else {
+            mInjector.binderWithCleanCallingIdentity(() -> {
+                mUserManager.setApplicationRestrictions(packageName, restrictions,
+                        caller.getUserHandle());
+            });
+        }
+
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.SET_APPLICATION_RESTRICTIONS)
+                .setAdmin(caller.getPackageName())
+                .setBoolean(/* isDelegate */ who == null)
+                .setStrings(packageName)
+                .write();
+    }
+
+    /**
+     * Set app restrictions in user manager to keep backwards compatibility for the old
+     * getApplicationRestrictions API.
+     */
+    private void setBackwardsCompatibleAppRestrictions(
+            String packageName, Bundle restrictions, UserHandle userHandle) {
+        Bundle restrictionsToApply = restrictions == null || restrictions.isEmpty()
+                ? getAppRestrictionsSetByAnyAdmin(packageName, userHandle)
+                : restrictions;
         mInjector.binderWithCleanCallingIdentity(() -> {
-            mUserManager.setApplicationRestrictions(packageName, settings,
-                    caller.getUserHandle());
-            DevicePolicyEventLogger
-                    .createEvent(DevicePolicyEnums.SET_APPLICATION_RESTRICTIONS)
-                    .setAdmin(caller.getPackageName())
-                    .setBoolean(/* isDelegate */ who == null)
-                    .setStrings(packageName)
-                    .write();
+            mUserManager.setApplicationRestrictions(packageName, restrictionsToApply, userHandle);
         });
     }
 
+    private Bundle getAppRestrictionsSetByAnyAdmin(String packageName, UserHandle userHandle) {
+        LinkedHashMap<EnforcingAdmin, PolicyValue<Bundle>> policies =
+                mDevicePolicyEngine.getLocalPoliciesSetByAdmins(
+                        PolicyDefinition.APPLICATION_RESTRICTIONS(packageName),
+                        userHandle.getIdentifier());
+        return policies.isEmpty()
+                ? null : policies.entrySet().stream().findAny().get().getValue().getValue();
+    }
+
     @Override
     public void setTrustAgentConfiguration(ComponentName admin, ComponentName agent,
             PersistableBundle args, boolean parent) {
@@ -11339,6 +11431,11 @@
         }
 
         final int userId = user.id;
+
+        if (isDevicePolicyEngineFlagEnabled()) {
+            mDevicePolicyEngine.handleUserCreated(user);
+        }
+
         if (token != null) {
             synchronized (getLockObject()) {
                 if (mPendingUserCreatedCallbackTokens.contains(token)) {
@@ -11708,13 +11805,28 @@
                 && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
                 || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_APP_RESTRICTIONS)));
 
-        return mInjector.binderWithCleanCallingIdentity(() -> {
-            Bundle bundle = mUserManager.getApplicationRestrictions(packageName,
-                    caller.getUserHandle());
-           // if no restrictions were saved, mUserManager.getApplicationRestrictions
-           // returns null, but DPM method should return an empty Bundle as per JavaDoc
-           return bundle != null ? bundle : Bundle.EMPTY;
-        });
+        if (useDevicePolicyEngine(caller, DELEGATION_APP_RESTRICTIONS)) {
+            EnforcingAdmin admin = EnforcingAdmin.createEnterpriseEnforcingAdmin(
+                    who != null ? who : new ComponentName(callerPackage, "Delegate"),
+                    caller.getUserId());
+
+            LinkedHashMap<EnforcingAdmin, PolicyValue<Bundle>> policies =
+                    mDevicePolicyEngine.getLocalPoliciesSetByAdmins(
+                            PolicyDefinition.APPLICATION_RESTRICTIONS(packageName),
+                            caller.getUserId());
+            if (policies.isEmpty() || !policies.containsKey(admin)) {
+                return Bundle.EMPTY;
+            }
+            return policies.get(admin).getValue();
+        } else {
+            return mInjector.binderWithCleanCallingIdentity(() -> {
+                Bundle bundle = mUserManager.getApplicationRestrictions(packageName,
+                        caller.getUserHandle());
+                // if no restrictions were saved, mUserManager.getApplicationRestrictions
+                // returns null, but DPM method should return an empty Bundle as per JavaDoc
+                return bundle != null ? bundle : Bundle.EMPTY;
+            });
+        }
     }
 
     /**
@@ -12365,7 +12477,7 @@
                 || isFinancedDeviceOwner(caller)))
                 || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_BLOCK_UNINSTALL)));
 
-        if (isCoexistenceEnabled(caller)) {
+        if (useDevicePolicyEngine(caller, DELEGATION_BLOCK_UNINSTALL)) {
             // TODO(b/260573124): Add correct enforcing admin when permission changes are
             //  merged, and don't forget to handle delegates! Enterprise admins assume
             //  component name isn't null.
@@ -12375,7 +12487,7 @@
             mDevicePolicyEngine.setLocalPolicy(
                     PolicyDefinition.PACKAGE_UNINSTALL_BLOCKED(packageName),
                     admin,
-                    uninstallBlocked,
+                    new BooleanPolicyValue(uninstallBlocked),
                     caller.getUserId());
         } else {
             final int userId = caller.getUserId();
@@ -12929,7 +13041,7 @@
             checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_LOCK_TASK_PACKAGES);
         }
 
-        if (isCoexistenceEnabled(caller)) {
+        if (useDevicePolicyEngine(caller, /* delegateScope= */ null)) {
             EnforcingAdmin admin = EnforcingAdmin.createEnterpriseEnforcingAdmin(
                     who, caller.getUserId());
             if (packages.length == 0) {
@@ -12946,7 +13058,7 @@
                 if (currentPolicy == null) {
                     policy = new LockTaskPolicy(Set.of(packages));
                 } else {
-                    policy = currentPolicy.clone();
+                    policy = new LockTaskPolicy(currentPolicy);
                     policy.setPackages(Set.of(packages));
                 }
 
@@ -12983,7 +13095,7 @@
             enforceCanCallLockTaskLocked(caller);
         }
 
-        if (isCoexistenceEnabled(caller)) {
+        if (useDevicePolicyEngine(caller, /* delegateScope= */ null)) {
             LockTaskPolicy policy = mDevicePolicyEngine.getResolvedPolicy(
                     PolicyDefinition.LOCK_TASK, userHandle);
             if (policy == null) {
@@ -13011,9 +13123,8 @@
         }
 
         final int userId = mInjector.userHandleGetCallingUserId();
-        // TODO(b/260560985): This is not the right check, as the flag could be enabled but there
-        //  could be an admin that hasn't targeted U.
-        if (isCoexistenceFlagEnabled()) {
+        // Is it ok to just check that no active policies exist currently?
+        if (mDevicePolicyEngine.hasActivePolicies()) {
             LockTaskPolicy policy = mDevicePolicyEngine.getResolvedPolicy(
                     PolicyDefinition.LOCK_TASK, userId);
             if (policy == null) {
@@ -13047,7 +13158,7 @@
             enforceCanSetLockTaskFeaturesOnFinancedDevice(caller, flags);
             checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_LOCK_TASK_FEATURES);
         }
-        if (isCoexistenceEnabled(caller)) {
+        if (useDevicePolicyEngine(caller, /* delegateScope= */ null)) {
             EnforcingAdmin admin = EnforcingAdmin.createEnterpriseEnforcingAdmin(who, userHandle);
             LockTaskPolicy currentPolicy = mDevicePolicyEngine.getLocalPolicySetByAdmin(
                     PolicyDefinition.LOCK_TASK,
@@ -13057,7 +13168,7 @@
                 throw new IllegalArgumentException("Can't set a lock task flags without setting "
                         + "lock task packages first.");
             }
-            LockTaskPolicy policy = currentPolicy.clone();
+            LockTaskPolicy policy = new LockTaskPolicy(currentPolicy);
             policy.setFlags(flags);
 
             mDevicePolicyEngine.setLocalPolicy(
@@ -13088,7 +13199,7 @@
             enforceCanCallLockTaskLocked(caller);
         }
 
-        if (isCoexistenceEnabled(caller)) {
+        if (useDevicePolicyEngine(caller, /* delegateScope= */ null)) {
             LockTaskPolicy policy = mDevicePolicyEngine.getResolvedPolicy(
                     PolicyDefinition.LOCK_TASK, userHandle);
             if (policy == null) {
@@ -14233,6 +14344,37 @@
                     APPLICATION_EXEMPTIONS_FLAG,
                     DEFAULT_APPLICATION_EXEMPTIONS_FLAG);
         }
+
+        @Override
+        public Map<String, Bundle> getApplicationRestrictionsPerAdmin(
+                String packageName, int userId) {
+            LinkedHashMap<EnforcingAdmin, PolicyValue<Bundle>> policies =
+                    mDevicePolicyEngine.getLocalPoliciesSetByAdmins(
+                            PolicyDefinition.APPLICATION_RESTRICTIONS(packageName),
+                            userId);
+            Map<String, Bundle> restrictions = new HashMap<>();
+            for (EnforcingAdmin admin : policies.keySet()) {
+                restrictions.put(admin.getPackageName(), policies.get(admin).getValue());
+            }
+            if (!restrictions.isEmpty()) {
+                return restrictions;
+            }
+
+            return mInjector.binderWithCleanCallingIdentity(() -> {
+                // Could be a device that hasn't migrated yet, so just return any restrictions saved
+                // in userManager.
+                Bundle bundle = mUserManager.getApplicationRestrictions(
+                        packageName, UserHandle.of(userId));
+                if (bundle == null || bundle.isEmpty()) {
+                    return new HashMap<>();
+                }
+                ActiveAdmin admin = getMostProbableDPCAdminForLocalPolicy(userId);
+                if (admin == null) {
+                    return new HashMap<>();
+                }
+                return Map.of(admin.info.getPackageName(), bundle);
+            });
+        }
     }
 
     private Intent createShowAdminSupportIntent(int userId) {
@@ -14760,7 +14902,11 @@
                 enforcePermissionGrantStateOnFinancedDevice(packageName, permission);
             }
         }
-        if (isCoexistenceEnabled(caller)) {
+        if (useDevicePolicyEngine(caller, DELEGATION_PERMISSION_GRANT)) {
+            // TODO(b/266924257): decide how to handle the internal state if the package doesn't
+            //  exist, or the permission isn't requested by the app, because we could end up with
+            //  inconsistent state between the policy engine and package manager. Also a package
+            //  might get removed or has it's permission updated after we've set the policy.
             mDevicePolicyEngine.setLocalPolicy(
                     PolicyDefinition.PERMISSION_GRANT(packageName, permission),
                     // TODO(b/260573124): Add correct enforcing admin when permission changes are
@@ -14768,7 +14914,7 @@
                     //  component name isn't null.
                     EnforcingAdmin.createEnterpriseEnforcingAdmin(
                             caller.getComponentName(), caller.getUserId()),
-                    grantState,
+                    new IntegerPolicyValue(grantState),
                     caller.getUserId());
             // TODO: update javadoc to reflect that callback no longer return success/failure
             callback.sendResult(Bundle.EMPTY);
@@ -14855,44 +15001,50 @@
                 enforcePermissionGrantStateOnFinancedDevice(packageName, permission);
             }
             return mInjector.binderWithCleanCallingIdentity(() -> {
-                int granted;
-                if (getTargetSdk(caller.getPackageName(), caller.getUserId())
-                        < android.os.Build.VERSION_CODES.Q) {
-                    // The per-Q behavior was to not check the app-ops state.
-                    granted = mIPackageManager.checkPermission(permission, packageName,
-                            caller.getUserId());
-                } else {
-                    try {
-                        int uid = mInjector.getPackageManager().getPackageUidAsUser(packageName,
-                                caller.getUserId());
-                        if (PermissionChecker.checkPermissionForPreflight(mContext, permission,
-                                PermissionChecker.PID_UNKNOWN, uid, packageName)
-                                        != PermissionChecker.PERMISSION_GRANTED) {
-                            granted = PackageManager.PERMISSION_DENIED;
-                        } else {
-                            granted = PackageManager.PERMISSION_GRANTED;
-                        }
-                    } catch (NameNotFoundException e) {
-                        // Package does not exit
-                        return DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT;
-                    }
-                }
-                int permFlags = mInjector.getPackageManager().getPermissionFlags(
-                        permission, packageName, caller.getUserHandle());
-                if ((permFlags & PackageManager.FLAG_PERMISSION_POLICY_FIXED)
-                        != PackageManager.FLAG_PERMISSION_POLICY_FIXED) {
-                    // Not controlled by policy
-                    return DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT;
-                } else {
-                    // Policy controlled so return result based on permission grant state
-                    return granted == PackageManager.PERMISSION_GRANTED
-                            ? DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED
-                            : DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED;
-                }
+                return getPermissionGrantStateForUser(
+                        packageName, permission, caller, caller.getUserId());
             });
         }
     }
 
+    private int getPermissionGrantStateForUser(
+            String packageName, String permission, CallerIdentity caller, int userId)
+            throws RemoteException {
+        int granted;
+        if (getTargetSdk(caller.getPackageName(), caller.getUserId())
+                < android.os.Build.VERSION_CODES.Q) {
+            // The per-Q behavior was to not check the app-ops state.
+            granted = mIPackageManager.checkPermission(permission, packageName, userId);
+        } else {
+            try {
+                int uid = mInjector.getPackageManager().getPackageUidAsUser(
+                        packageName, userId);
+                if (PermissionChecker.checkPermissionForPreflight(mContext, permission,
+                        PermissionChecker.PID_UNKNOWN, uid, packageName)
+                        != PermissionChecker.PERMISSION_GRANTED) {
+                    granted = PackageManager.PERMISSION_DENIED;
+                } else {
+                    granted = PackageManager.PERMISSION_GRANTED;
+                }
+            } catch (NameNotFoundException e) {
+                // Package does not exit
+                return DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT;
+            }
+        }
+        int permFlags = mInjector.getPackageManager().getPermissionFlags(
+                permission, packageName, UserHandle.of(userId));
+        if ((permFlags & PackageManager.FLAG_PERMISSION_POLICY_FIXED)
+                != PackageManager.FLAG_PERMISSION_POLICY_FIXED) {
+            // Not controlled by policy
+            return DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT;
+        } else {
+            // Policy controlled so return result based on permission grant state
+            return granted == PackageManager.PERMISSION_GRANTED
+                    ? DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED
+                    : DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED;
+        }
+    }
+
     boolean isPackageInstalledForUser(String packageName, int userHandle) {
         return mInjector.binderWithCleanCallingIdentity(() -> {
             try {
@@ -15432,16 +15584,27 @@
     }
 
     @Override
-    public void setOrganizationName(@NonNull ComponentName who, CharSequence text) {
+    public void setOrganizationName(@Nullable ComponentName who, CharSequence text) {
         if (!mHasFeature) {
             return;
         }
-        Objects.requireNonNull(who, "ComponentName is null");
-        final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+        CallerIdentity caller = getCallerIdentity(who);
+        ActiveAdmin admin = null;
+
+        if (isPermissionCheckFlagEnabled()) {
+            EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
+                    who,
+                    MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY,
+                    caller.getUserId());
+            admin = enforcingAdmin.getActiveAdmin();
+        } else {
+            Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+        }
 
         synchronized (getLockObject()) {
-            ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
+            if (!isPermissionCheckFlagEnabled()) {
+                admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
+            }
             if (!TextUtils.equals(admin.organizationName, text)) {
                 admin.organizationName = (text == null || text.length() == 0)
                         ? null : text.toString();
@@ -15451,20 +15614,29 @@
     }
 
     @Override
-    public CharSequence getOrganizationName(@NonNull ComponentName who) {
+    public CharSequence getOrganizationName(@Nullable ComponentName who) {
         if (!mHasFeature) {
             return null;
         }
-        Objects.requireNonNull(who, "ComponentName is null");
+        CallerIdentity caller = getCallerIdentity(who);
+        ActiveAdmin admin = null;
 
-        final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallingUser(isManagedProfile(caller.getUserId()));
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+        if (isPermissionCheckFlagEnabled()) {
+            EnforcingAdmin enforcingAdmin = enforceCanQueryAndGetEnforcingAdmin(
+                    who,
+                    MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY,
+                    caller.getUserId());
+            admin = enforcingAdmin.getActiveAdmin();
+        } else {
+            Preconditions.checkCallingUser(isManagedProfile(caller.getUserId()));
+            Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
 
-        synchronized (getLockObject()) {
-            ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
-            return admin.organizationName;
+            synchronized (getLockObject()) {
+                admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
+            }
         }
+
+        return admin.organizationName;
     }
 
     /**
@@ -16150,6 +16322,11 @@
         // The removed admin might have disabled camera, so update user
         // restrictions.
         pushUserRestrictions(userHandle);
+
+        // The removed admin might've been stopping the migration if it was targeting pre Android U
+        if (shouldMigrateToDevicePolicyEngine()) {
+            migratePoliciesToDevicePolicyEngine();
+        }
     }
 
     @Override
@@ -18043,7 +18220,7 @@
         checkCanExecuteOrThrowUnsafe(
                 DevicePolicyManager.OPERATION_SET_USER_CONTROL_DISABLED_PACKAGES);
 
-        if (isCoexistenceEnabled(caller)) {
+        if (useDevicePolicyEngine(caller, /* delegateScope= */ null)) {
             Binder.withCleanCallingIdentity(() -> {
                 if (packages.isEmpty()) {
                     removeUserControlDisabledPackages(caller);
@@ -18077,7 +18254,7 @@
                     //  merged.
                     EnforcingAdmin.createEnterpriseEnforcingAdmin(
                             caller.getComponentName(), caller.getUserId()),
-                    packages);
+                    new StringSetPolicyValue(packages));
         } else {
             mDevicePolicyEngine.setLocalPolicy(
                     PolicyDefinition.USER_CONTROLLED_DISABLED_PACKAGES,
@@ -18085,7 +18262,7 @@
                     //  merged.
                     EnforcingAdmin.createEnterpriseEnforcingAdmin(
                             caller.getComponentName(), caller.getUserId()),
-                    packages,
+                    new StringSetPolicyValue(packages),
                     caller.getUserId());
         }
     }
@@ -18123,7 +18300,7 @@
         Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller) || isProfileOwner(caller)
                 || isFinancedDeviceOwner(caller));
 
-        if (isCoexistenceEnabled(caller)) {
+        if (useDevicePolicyEngine(caller, /* delegateScope= */ null)) {
             // This retrieves the policy for the calling user only, DOs for example can't know
             // what's enforced globally or on another user.
             Set<String> packages = mDevicePolicyEngine.getResolvedPolicy(
@@ -20088,22 +20265,26 @@
             MANAGE_DEVICE_POLICY_ACROSS_USERS,
             MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL,
             SET_TIME,
-            SET_TIME_ZONE);
+            SET_TIME_ZONE,
+            MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY);
     private static final List<String> FINANCED_DEVICE_OWNER_PERMISSIONS = List.of(
             MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL,
             MANAGE_DEVICE_POLICY_ACROSS_USERS,
-            MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL);
+            MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL,
+            MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY);
     private static final List<String> PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE_PERMISSIONS =
             List.of(
                 MANAGE_DEVICE_POLICY_ACROSS_USERS,
                 MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL,
                 SET_TIME,
-                SET_TIME_ZONE);
+                SET_TIME_ZONE,
+                MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY);
     private static final List<String> PROFILE_OWNER_ON_USER_0_PERMISSIONS  = List.of(
             SET_TIME,
             SET_TIME_ZONE);
     private static final List<String> PROFILE_OWNER_PERMISSIONS  = List.of(
-            MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL);
+            MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL,
+            MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY);
 
     private static final HashMap<Integer, List<String>> DPC_PERMISSIONS = new HashMap<>();
     {
@@ -20121,9 +20302,53 @@
     private static final HashMap<String, String> CROSS_USER_PERMISSIONS =
             new HashMap<>();
     {
-        // Auto time is intrinsically global so there is no cross-user permission.
+        // Time and Timezone is intrinsically global so there is no cross-user permission.
         CROSS_USER_PERMISSIONS.put(SET_TIME, null);
         CROSS_USER_PERMISSIONS.put(SET_TIME_ZONE, null);
+        // Organisation identity policy will involve data of other organisations on the device and
+        // therefore the FULL cross-user permission is required.
+        CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY,
+                MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
+    }
+
+    /**
+     * Checks if the calling process has been granted permission to apply a device policy on a
+     * specific user.
+     * The given permission will be checked along with its associated cross-user permission if it
+     * exists and the target user is different to the calling user.
+     * Returns the {@link ActiveAdmin} of the caller.
+     *
+     * @param permission The name of the permission being checked.
+     * @param targetUserId The userId of the user which the caller needs permission to act on.
+     * @throws SecurityException if the caller has not been granted the given permission,
+     * the associated cross-user permission if the caller's user is different to the target user.
+     */
+    private EnforcingAdmin enforcePermissionAndGetEnforcingAdmin(@Nullable ComponentName admin,
+            String permission, int targetUserId) {
+        enforcePermission(permission, targetUserId);
+        return getEnforcingAdminForCaller(admin, getCallerIdentity());
+    }
+
+    /**
+     * Checks whether the calling process has been granted permission to query a device policy on
+     * a specific user.
+     * The given permission will be checked along with its associated cross-user permission if it
+     * exists and the target user is different to the calling user.
+     *
+     * @param permission The name of the permission being checked.
+     * @param targetUserId The userId of the user which the caller needs permission to act on.
+     * @throws SecurityException if the caller has not been granted the given permission,
+     * the associated cross-user permission if the caller's user is different to the target user.
+     */
+    private EnforcingAdmin enforceCanQueryAndGetEnforcingAdmin(@Nullable ComponentName admin,
+            String permission, int targetUserId) {
+        enforceCanQuery(permission, targetUserId);
+        return getEnforcingAdminForCaller(admin, getCallerIdentity());
+    }
+
+    private static final HashMap<String, String> POLICY_IDENTIFIER_TO_PERMISSION = new HashMap<>();
+    {
+        POLICY_IDENTIFIER_TO_PERMISSION.put(AUTO_TIMEZONE_POLICY, SET_TIME_ZONE);
     }
 
     /**
@@ -20135,7 +20360,7 @@
      * @param permission The name of the permission being checked.
      * @param targetUserId The userId of the user which the caller needs permission to act on.
      * @throws SecurityException if the caller has not been granted the given permission,
-     * the associtated cross-user permission if the caller's user is different to the target user.
+     * the associated cross-user permission if the caller's user is different to the target user.
      */
     private void enforcePermission(String permission, int targetUserId)
             throws SecurityException {
@@ -20150,13 +20375,15 @@
     }
 
     /**
-     * Return whether the calling process has been granted permission to query a device policy on
+     * Checks whether the calling process has been granted permission to query a device policy on
      * a specific user.
+     * The given permission will be checked along with its associated cross-user permission if it
+     * exists and the target user is different to the calling user.
      *
      * @param permission The name of the permission being checked.
      * @param targetUserId The userId of the user which the caller needs permission to act on.
      * @throws SecurityException if the caller has not been granted the given permission,
-     * the associatated cross-user permission if the caller's user is different to the target user
+     * the associated cross-user permission if the caller's user is different to the target user
      * and if the user has not been granted {@link QUERY_ADMIN_POLICY}.
      */
     private void enforceCanQuery(String permission, int targetUserId) throws SecurityException {
@@ -20221,10 +20448,15 @@
         }
         // Check if the caller is an active admin that uses a certain policy.
         if (ACTIVE_ADMIN_POLICIES.containsKey(permission)) {
-            return getActiveAdminForCallerLocked(
-                    null, ACTIVE_ADMIN_POLICIES.get(permission), false) != null;
+            try {
+                return getActiveAdminForCallerLocked(
+                        null, ACTIVE_ADMIN_POLICIES.get(permission), false) != null;
+            } catch (SecurityException e) {
+                // A security exception means there is not an active admin with permission and
+                // therefore
+                return false;
+            }
         }
-
         return false;
     }
 
@@ -20254,6 +20486,22 @@
         return false;
     }
 
+    private EnforcingAdmin getEnforcingAdminForCaller(@Nullable ComponentName who,
+            CallerIdentity caller) {
+        int userId = caller.getUserId();
+        ActiveAdmin admin = null;
+        synchronized (getLockObject()) {
+            admin = getActiveAdminUncheckedLocked(who, userId);
+        }
+        if (isDeviceOwner(caller) || isProfileOwner(caller)) {
+            return EnforcingAdmin.createEnterpriseEnforcingAdmin(who, userId, admin);
+        }
+        if (getActiveAdminUncheckedLocked(who, userId) != null) {
+            return EnforcingAdmin.createDeviceAdminEnforcingAdmin(who, userId, admin);
+        }
+        return  EnforcingAdmin.createEnforcingAdmin(caller.getPackageName(), userId);
+    }
+
     private boolean isPermissionCheckFlagEnabled() {
         return DeviceConfig.getBoolean(
                 NAMESPACE_DEVICE_POLICY_MANAGER,
@@ -20261,20 +20509,6 @@
                 DEFAULT_VALUE_PERMISSION_BASED_ACCESS_FLAG);
     }
 
-    // TODO(b/260560985): properly gate coexistence changes
-    private boolean isCoexistenceEnabled(CallerIdentity caller) {
-        return isCoexistenceFlagEnabled()
-                && mInjector.isChangeEnabled(
-                        ENABLE_COEXISTENCE_CHANGE, caller.getPackageName(), caller.getUserId());
-    }
-
-    private boolean isCoexistenceFlagEnabled() {
-        return DeviceConfig.getBoolean(
-                NAMESPACE_DEVICE_POLICY_MANAGER,
-                ENABLE_COEXISTENCE_FLAG,
-                DEFAULT_ENABLE_COEXISTENCE_FLAG);
-    }
-
     private static boolean isKeepProfilesRunningFlagEnabled() {
         return DeviceConfig.getBoolean(
                 NAMESPACE_DEVICE_POLICY_MANAGER,
@@ -20427,7 +20661,7 @@
             final long id = mInjector.binderClearCallingIdentity();
             try {
                 int parentUserId = getProfileParentId(caller.getUserId());
-                installOemDefaultDialerAndMessagesApp(parentUserId, caller.getUserId());
+                installOemDefaultDialerAndSmsApp(caller.getUserId());
                 updateTelephonyCrossProfileIntentFilters(parentUserId, caller.getUserId(), true);
             } finally {
                 mInjector.binderRestoreCallingIdentity(id);
@@ -20435,32 +20669,31 @@
         }
     }
 
-    private void installOemDefaultDialerAndMessagesApp(int sourceUserId, int targetUserId) {
+    private void installOemDefaultDialerAndSmsApp(int targetUserId) {
         try {
-            UserHandle sourceUserHandle = UserHandle.of(sourceUserId);
-            TelecomManager telecomManager = mContext.getSystemService(TelecomManager.class);
-            String dialerAppPackage = telecomManager.getDefaultDialerPackage(
-                    sourceUserHandle);
-            String messagesAppPackage = SmsApplication.getDefaultSmsApplicationAsUser(mContext,
-                    true, sourceUserHandle).getPackageName();
-            if (dialerAppPackage != null) {
-                mIPackageManager.installExistingPackageAsUser(dialerAppPackage, targetUserId,
-                        PackageManager.INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS,
+            String defaultDialerPackageName = getDefaultRoleHolderPackageName(
+                    com.android.internal.R.string.config_defaultDialer);
+            String defaultSmsPackageName = getDefaultRoleHolderPackageName(
+                    com.android.internal.R.string.config_defaultSms);
+
+            if (defaultDialerPackageName != null) {
+                mIPackageManager.installExistingPackageAsUser(defaultDialerPackageName,
+                        targetUserId, PackageManager.INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS,
                         PackageManager.INSTALL_REASON_POLICY, null);
             } else {
                 Slogf.w(LOG_TAG, "Couldn't install dialer app, dialer app package is null");
             }
 
-            if (messagesAppPackage != null) {
-                mIPackageManager.installExistingPackageAsUser(messagesAppPackage, targetUserId,
+            if (defaultSmsPackageName != null) {
+                mIPackageManager.installExistingPackageAsUser(defaultSmsPackageName, targetUserId,
                         PackageManager.INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS,
                         PackageManager.INSTALL_REASON_POLICY, null);
             } else {
-                Slogf.w(LOG_TAG, "Couldn't install messages app, messages app package is null");
+                Slogf.w(LOG_TAG, "Couldn't install sms app, sms app package is null");
             }
         } catch (RemoteException re) {
             // shouldn't happen
-            Slogf.wtf(LOG_TAG, "Failed to install dialer/messages app", re);
+            Slogf.wtf(LOG_TAG, "Failed to install dialer/sms app", re);
         }
     }
 
@@ -20519,4 +20752,363 @@
             }
         }
     }
+
+    @Override
+    public DevicePolicyState getDevicePolicyState() {
+        Preconditions.checkCallAuthorization(
+                hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
+        return mInjector.binderWithCleanCallingIdentity(mDevicePolicyEngine::getDevicePolicyState);
+    }
+
+    @Override
+    public boolean triggerDevicePolicyEngineMigration(boolean forceMigration) {
+        Preconditions.checkCallAuthorization(
+                hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
+        return mInjector.binderWithCleanCallingIdentity(() -> {
+            boolean canForceMigration = forceMigration && !hasNonTestOnlyActiveAdmins();
+            if (!canForceMigration && !shouldMigrateToDevicePolicyEngine()) {
+                return false;
+            }
+            return migratePoliciesToDevicePolicyEngine();
+        });
+    }
+
+    private boolean hasNonTestOnlyActiveAdmins() {
+        return mInjector.binderWithCleanCallingIdentity(() -> {
+            for (UserInfo userInfo : mUserManager.getUsers()) {
+                List<ComponentName> activeAdmins = getActiveAdmins(userInfo.id);
+                if (activeAdmins == null) {
+                    continue;
+                }
+                for (ComponentName admin : activeAdmins) {
+                    if (!isAdminTestOnlyLocked(admin, userInfo.id)) {
+                        return true;
+                    }
+                }
+            }
+            return false;
+        });
+    }
+
+    // TODO(b/266808047): handle DeviceAdmin migration when there is no DPCs on the device
+    private boolean shouldMigrateToDevicePolicyEngine() {
+        return mInjector.binderWithCleanCallingIdentity(() -> {
+            if (!isDevicePolicyEngineFlagEnabled()) {
+                return false;
+            }
+            if (mOwners.isMigratedToPolicyEngine()) {
+                return false;
+            }
+            // We're only checking if existing DPCs are not targeting U, regardless of what
+            // DeviceAdmins are targeting, as they can access very limited APIs, and we'll ensure
+            // that these APIs maintain the current behaviour of strictest applies.
+            boolean hasDPCs = false;
+            for (UserInfo userInfo : mUserManager.getUsers()) {
+                List<ComponentName> activeAdmins = getActiveAdmins(userInfo.id);
+                if (activeAdmins == null) {
+                    continue;
+                }
+                for (ComponentName admin : activeAdmins) {
+                    if ((isProfileOwner(admin, userInfo.id) || isDeviceOwner(admin, userInfo.id))) {
+                        if (!mInjector.isChangeEnabled(ENABLE_COEXISTENCE_CHANGE,
+                                admin.getPackageName(), userInfo.id)) {
+                            return false;
+                        }
+                        hasDPCs = true;
+                    }
+                }
+            }
+            return hasDPCs;
+        });
+    }
+
+    /**
+     * @return {@code true} if policies were migrated successfully, {@code false} otherwise.
+     */
+    private boolean migratePoliciesToDevicePolicyEngine() {
+        return mInjector.binderWithCleanCallingIdentity(() -> {
+            try {
+                Slogf.i(LOG_TAG, "Started device policies migration to the device policy engine.");
+                migrateAutoTimezonePolicy();
+                migratePermissionGrantStatePolicies();
+                // TODO(b/258811766): add migration logic for all policies
+
+                mOwners.markMigrationToPolicyEngine();
+                return true;
+            } catch (Exception e) {
+                mDevicePolicyEngine.clearAllPolicies();
+                Slogf.e(LOG_TAG, e, "Error occurred during device policy migration, will "
+                        + "reattempt on the next system server restart.");
+                return false;
+            }
+        });
+    }
+
+    private void migrateAutoTimezonePolicy() {
+        Slogf.i(LOG_TAG, "Skipping Migration of AUTO_TIMEZONE policy to device policy engine,"
+                + "as no way to identify if the value was set by the admin or the user.");
+    }
+
+    private void migratePermissionGrantStatePolicies() {
+        Slogf.i(LOG_TAG, "Migrating PERMISSION_GRANT policy to device policy engine.");
+        for (UserInfo userInfo : mUserManager.getUsers()) {
+            ActiveAdmin admin = getMostProbableDPCAdminForLocalPolicy(userInfo.id);
+            if (admin == null) {
+                Slogf.i(LOG_TAG, "No admin found that can set permission grant state on user "
+                        + userInfo.id);
+                continue;
+            }
+            for (PackageInfo packageInfo : getInstalledPackagesOnUser(userInfo.id)) {
+                if (packageInfo.requestedPermissions == null) {
+                    continue;
+                }
+                for (String permission : packageInfo.requestedPermissions) {
+                    if (!isRuntimePermission(permission)) {
+                        continue;
+                    }
+                    int grantState = DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT;
+                    try {
+                        grantState = getPermissionGrantStateForUser(
+                                packageInfo.packageName, permission,
+                                new CallerIdentity(
+                                        mInjector.binderGetCallingUid(),
+                                        admin.info.getComponent().getPackageName(),
+                                        admin.info.getComponent()),
+                                userInfo.id);
+                    } catch (RemoteException e) {
+                        Slogf.e(LOG_TAG, e, "Error retrieving permission grant state for %s "
+                                        + "and %s", packageInfo.packageName, permission);
+                    }
+                    if (grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT) {
+                        // Not Controlled by a policy
+                        continue;
+                    }
+
+                    mDevicePolicyEngine.setLocalPolicy(
+                            PolicyDefinition.PERMISSION_GRANT(packageInfo.packageName,
+                                    permission),
+                            EnforcingAdmin.createEnterpriseEnforcingAdmin(
+                                    admin.info.getComponent(),
+                                    admin.getUserHandle().getIdentifier()),
+                            new IntegerPolicyValue(grantState),
+                            userInfo.id,
+                            /* skipEnforcePolicy= */ true);
+                }
+            }
+        }
+    }
+
+    private List<PackageInfo> getInstalledPackagesOnUser(int userId) {
+        return mInjector.binderWithCleanCallingIdentity(() ->
+                mContext.getPackageManager().getInstalledPackagesAsUser(
+                        PackageManager.PackageInfoFlags.of(
+                                PackageManager.GET_PERMISSIONS), userId));
+    }
+
+    /**
+     * Returns the most probable admin to have set a global policy according to the following
+     * heuristics:
+     *
+     * <ul>
+     * <li>The device owner on any user</li>
+     * <li>The org owned profile owner on any user</li>
+     * <li>The profile owner on any user</li>
+     * </ul>
+     */
+    @Nullable
+    // TODO(b/266928216): Check what the admin capabilities are when deciding which admin to return.
+    private ActiveAdmin getMostProbableDPCAdminForGlobalPolicy() {
+        synchronized (getLockObject()) {
+            ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
+            if (deviceOwner != null) {
+                return deviceOwner;
+            }
+
+            List<UserInfo> users = mUserManager.getUsers();
+            for (UserInfo userInfo : users) {
+                if (isProfileOwnerOfOrganizationOwnedDevice(userInfo.id)) {
+                    return getProfileOwnerAdminLocked(userInfo.id);
+                }
+            }
+
+            for (UserInfo userInfo : users) {
+                ActiveAdmin profileOwner = getProfileOwnerLocked(userInfo.id);
+                if (profileOwner != null) {
+                    return profileOwner;
+                }
+            }
+            return null;
+        }
+    }
+
+    /**
+     * Returns the most probable admin to have set a policy on the given {@code userId} according
+     * to the following heuristics:
+     *
+     * <ul>
+     * <li>The device owner on the given userId</li>
+     * <li>The profile owner on the given userId</li>
+     * <li>The org owned profile owner of which the given userId is its parent</li>
+     * <li>The profile owner of which the given userId is its parent</li>
+     * <li>The device owner on any user</li>
+     * <li>The profile owner on any user</li>
+     * </ul>
+     */
+    @Nullable
+    // TODO(b/266928216): Check what the admin capabilities are when deciding which admin to return.
+    private ActiveAdmin getMostProbableDPCAdminForLocalPolicy(int userId) {
+        synchronized (getLockObject()) {
+            ActiveAdmin localDeviceOwner = getDeviceOwnerLocked(userId);
+            if (localDeviceOwner != null) {
+                return localDeviceOwner;
+            }
+
+            ActiveAdmin localProfileOwner = getProfileOwnerLocked(userId);
+            if (localProfileOwner != null) {
+                return localProfileOwner;
+            }
+
+            int[] profileIds = mUserManager.getProfileIds(userId, /* enabledOnly= */ false);
+            for (int id : profileIds) {
+                if (id == userId) {
+                    continue;
+                }
+                if (isProfileOwnerOfOrganizationOwnedDevice(id)) {
+                    return getProfileOwnerAdminLocked(id);
+                }
+            }
+
+            for (int id : profileIds) {
+                if (id == userId) {
+                    continue;
+                }
+                if (isManagedProfile(id)) {
+                    return getProfileOwnerAdminLocked(id);
+                }
+            }
+
+            ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
+            if (deviceOwner != null) {
+                return deviceOwner;
+            }
+
+            for (UserInfo userInfo : mUserManager.getUsers()) {
+                ActiveAdmin profileOwner = getProfileOwnerLocked(userInfo.id);
+                if (profileOwner != null) {
+                    return profileOwner;
+                }
+            }
+            return null;
+        }
+    }
+
+    // We need to add a mapping of policyId to permission in POLICY_IDENTIFIER_TO_PERMISSION
+    // for each migrated permission.
+    private List<ActiveAdmin> getNonDPCActiveAdminsForPolicyLocked(String policyIdentifier) {
+        String permission = POLICY_IDENTIFIER_TO_PERMISSION.get(policyIdentifier);
+        if (permission == null) {
+            Slogf.e(LOG_TAG, "Can't find a permission for %s in POLICY_IDENTIFIER_TO_PERMISSION",
+                    policyIdentifier);
+            return new ArrayList<>();
+        }
+        if (!ACTIVE_ADMIN_POLICIES.containsKey(permission)) {
+            return new ArrayList<>();
+        }
+
+        List<ActiveAdmin> admins = new ArrayList<>();
+        for (UserInfo userInfo : mUserManager.getUsers()) {
+            List<ComponentName> activeAdmins = getActiveAdmins(userInfo.id);
+            for (ComponentName admin : activeAdmins) {
+                if (isDeviceOwner(admin, userInfo.id) || isProfileOwner(admin, userInfo.id)) {
+                    continue;
+                }
+                DevicePolicyData policy = getUserDataUnchecked(userInfo.id);
+                if (isActiveAdminWithPolicyForUserLocked(
+                        policy.mAdminMap.get(admin), ACTIVE_ADMIN_POLICIES.get(permission),
+                        userInfo.id)) {
+                    admins.add(policy.mAdminMap.get(admin));
+                }
+            }
+        }
+        return admins;
+    }
+
+    // TODO: This can actually accept an EnforcingAdmin that gets created in the permission check
+    //  method.
+    private boolean useDevicePolicyEngine(CallerIdentity caller, @Nullable String delegateScope) {
+        if (!isCallerActiveAdminOrDelegate(caller, delegateScope)) {
+            if (!isDevicePolicyEngineFlagEnabled()) {
+                throw new IllegalStateException("Non DPC caller can't set device policies.");
+            }
+            if (hasDPCsNotSupportingCoexistence()) {
+                throw new IllegalStateException("Non DPC caller can't set device policies with "
+                        + "existing legacy admins on the device.");
+            }
+            return true;
+        } else {
+            return isDevicePolicyEngineFlagEnabled() && !hasDPCsNotSupportingCoexistence();
+        }
+    }
+
+    private boolean isDevicePolicyEngineFlagEnabled() {
+        return DeviceConfig.getBoolean(
+                NAMESPACE_DEVICE_POLICY_MANAGER,
+                ENABLE_DEVICE_POLICY_ENGINE_FLAG,
+                DEFAULT_ENABLE_DEVICE_POLICY_ENGINE_FLAG);
+    }
+
+    private boolean hasDPCsNotSupportingCoexistence() {
+        return mInjector.binderWithCleanCallingIdentity(() -> {
+            for (UserInfo userInfo : mUserManager.getUsers()) {
+                List<ComponentName> activeAdmins = getActiveAdmins(userInfo.id);
+                if (activeAdmins == null) {
+                    continue;
+                }
+                for (ComponentName admin : activeAdmins) {
+                    if ((isProfileOwner(admin, userInfo.id) || isDeviceOwner(admin, userInfo.id))
+                            && !mInjector.isChangeEnabled(ENABLE_COEXISTENCE_CHANGE,
+                            admin.getPackageName(), userInfo.id)) {
+                        return true;
+                    }
+                }
+            }
+            return false;
+        });
+    }
+
+    // TODO: this can actually accept an EnforcingAdmin that gets created in the permission
+    //  check method.
+    private boolean isCallerActiveAdminOrDelegate(
+            CallerIdentity caller, @Nullable String delegateScope) {
+        return mInjector.binderWithCleanCallingIdentity(() -> {
+            List<ComponentName> activeAdmins = getActiveAdmins(caller.getUserId());
+            if (activeAdmins != null) {
+                for (ComponentName admin : activeAdmins) {
+                    if (admin.getPackageName().equals(caller.getPackageName())) {
+                        return true;
+                    }
+                }
+            }
+            return delegateScope != null && isCallerDelegate(caller, delegateScope);
+        });
+    }
+
+    // TODO(b/266808047): This will return false for DeviceAdmins not targetting U, which is
+    //  inconsistent with the migration logic that allows migration with old DeviceAdmins.
+    private boolean canAddActiveAdminIfPolicyEngineEnabled(String packageName, int userId) {
+        if (!isDevicePolicyEngineFlagEnabled()) {
+            return true;
+        }
+        if (hasDPCsNotSupportingCoexistence()) {
+            return true;
+        }
+        if (mInjector.isChangeEnabled(ENABLE_COEXISTENCE_CHANGE, packageName, userId)) {
+            // This will always return true unless we turn off coexistence, in which case it will
+            // return true if no current admins exist, or more than one admin exist
+            return mDevicePolicyEngine.canAdminAddPolicies(packageName, userId);
+        }
+        // Is it ok to just check that no active policies exist currently, or should we return false
+        // if the policy engine was ever used?
+        return !mDevicePolicyEngine.hasActivePolicies();
+    }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
index da895f4..10e972d 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
@@ -18,7 +18,13 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.admin.Authority;
+import android.app.admin.UnknownAuthority;
+import android.app.admin.DeviceAdminAuthority;
+import android.app.admin.DpcAuthority;
+import android.app.admin.RoleAuthority;
 import android.content.ComponentName;
+import android.os.UserHandle;
 
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
@@ -63,6 +69,7 @@
     private Set<String> mAuthorities;
     private final int mUserId;
     private final boolean mIsRoleAuthority;
+    private final ActiveAdmin mActiveAdmin;
 
     static EnforcingAdmin createEnforcingAdmin(@NonNull String packageName, int userId) {
         Objects.requireNonNull(packageName);
@@ -73,14 +80,31 @@
             @NonNull ComponentName componentName, int userId) {
         Objects.requireNonNull(componentName);
         return new EnforcingAdmin(
-                componentName.getPackageName(), componentName, Set.of(DPC_AUTHORITY), userId);
+                componentName.getPackageName(), componentName, Set.of(DPC_AUTHORITY), userId,
+                /* activeAdmin=*/ null);
+    }
+
+    static EnforcingAdmin createEnterpriseEnforcingAdmin(
+            @NonNull ComponentName componentName, int userId, ActiveAdmin activeAdmin) {
+        Objects.requireNonNull(componentName);
+        return new EnforcingAdmin(
+                componentName.getPackageName(), componentName, Set.of(DPC_AUTHORITY), userId,
+                activeAdmin);
     }
 
     static EnforcingAdmin createDeviceAdminEnforcingAdmin(ComponentName componentName, int userId) {
         Objects.requireNonNull(componentName);
         return new EnforcingAdmin(
                 componentName.getPackageName(), componentName, Set.of(DEVICE_ADMIN_AUTHORITY),
-                userId);
+                userId, /* activeAdmin=*/ null);
+    }
+
+    static EnforcingAdmin createDeviceAdminEnforcingAdmin(ComponentName componentName, int userId,
+            ActiveAdmin activeAdmin) {
+        Objects.requireNonNull(componentName);
+        return new EnforcingAdmin(
+                componentName.getPackageName(), componentName, Set.of(DEVICE_ADMIN_AUTHORITY),
+                userId, activeAdmin);
     }
 
     static String getRoleAuthorityOf(String roleName) {
@@ -88,7 +112,8 @@
     }
 
     private EnforcingAdmin(
-            String packageName, ComponentName componentName, Set<String> authorities, int userId) {
+            String packageName, ComponentName componentName, Set<String> authorities, int userId,
+            ActiveAdmin activeAdmin) {
         Objects.requireNonNull(packageName);
         Objects.requireNonNull(componentName);
         Objects.requireNonNull(authorities);
@@ -99,6 +124,7 @@
         mComponentName = componentName;
         mAuthorities = new HashSet<>(authorities);
         mUserId = userId;
+        mActiveAdmin = activeAdmin;
     }
 
     private EnforcingAdmin(String packageName, int userId) {
@@ -111,6 +137,7 @@
         mComponentName = null;
         // authorities will be loaded when needed
         mAuthorities = null;
+        mActiveAdmin = null;
     }
 
     private static Set<String> getRoleAuthoritiesOrDefault(String packageName, int userId) {
@@ -156,6 +183,34 @@
         return mUserId;
     }
 
+    @Nullable
+    public ActiveAdmin getActiveAdmin() {
+        return mActiveAdmin;
+    }
+
+    @NonNull
+    android.app.admin.EnforcingAdmin getParcelableAdmin() {
+        Authority authority;
+        if (mIsRoleAuthority) {
+            Set<String> roles = getRoles(mPackageName, mUserId);
+            if (roles.isEmpty()) {
+                authority = UnknownAuthority.UNKNOWN_AUTHORITY;
+            } else {
+                authority = new RoleAuthority(roles);
+            }
+        } else if (mAuthorities.contains(DPC_AUTHORITY)) {
+            authority = DpcAuthority.DPC_AUTHORITY;
+        } else if (mAuthorities.contains(DEVICE_ADMIN_AUTHORITY)) {
+            authority = DeviceAdminAuthority.DEVICE_ADMIN_AUTHORITY;
+        } else {
+            authority = UnknownAuthority.UNKNOWN_AUTHORITY;
+        }
+        return new android.app.admin.EnforcingAdmin(
+                mPackageName,
+                authority,
+                UserHandle.of(mUserId));
+    }
+
     /**
      * For two EnforcingAdmins to be equal they must:
      *
@@ -224,7 +279,7 @@
             String className = parser.getAttributeValue(/* namespace= */ null, ATTR_CLASS_NAME);
             ComponentName componentName = new ComponentName(packageName, className);
             Set<String> authorities = Set.of(authoritiesStr.split(ATTR_AUTHORITIES_SEPARATOR));
-            return new EnforcingAdmin(packageName, componentName, authorities, userId);
+            return new EnforcingAdmin(packageName, componentName, authorities, userId, null);
         }
     }
 
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/IntegerUnion.java b/services/devicepolicy/java/com/android/server/devicepolicy/FlagUnion.java
similarity index 64%
rename from services/devicepolicy/java/com/android/server/devicepolicy/IntegerUnion.java
rename to services/devicepolicy/java/com/android/server/devicepolicy/FlagUnion.java
index a051a2b..6c4be21 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/IntegerUnion.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/FlagUnion.java
@@ -17,24 +17,32 @@
 package com.android.server.devicepolicy;
 
 import android.annotation.NonNull;
+import android.app.admin.IntegerPolicyValue;
+import android.app.admin.PolicyValue;
 
 import java.util.LinkedHashMap;
 import java.util.Objects;
 
-final class IntegerUnion extends ResolutionMechanism<Integer> {
+final class FlagUnion extends ResolutionMechanism<Integer> {
 
     @Override
-    Integer resolve(@NonNull LinkedHashMap<EnforcingAdmin, Integer> adminPolicies) {
+    IntegerPolicyValue resolve(
+            @NonNull LinkedHashMap<EnforcingAdmin, PolicyValue<Integer>> adminPolicies) {
         Objects.requireNonNull(adminPolicies);
         if (adminPolicies.isEmpty()) {
             return null;
         }
 
         Integer unionOfPolicies = 0;
-        for (Integer policy : adminPolicies.values()) {
-            unionOfPolicies |= policy;
+        for (PolicyValue<Integer> policy : adminPolicies.values()) {
+            unionOfPolicies |= policy.getValue();
         }
-        return unionOfPolicies;
+        return new IntegerPolicyValue(unionOfPolicies);
+    }
+
+    @Override
+    android.app.admin.FlagUnion getParcelableResolutionMechanism() {
+        return android.app.admin.FlagUnion.FLAG_UNION;
     }
 
     @Override
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/IntegerPolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/IntegerPolicySerializer.java
index d5949dd..bff6d32 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/IntegerPolicySerializer.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/IntegerPolicySerializer.java
@@ -18,6 +18,8 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.admin.IntegerPolicyValue;
+import android.app.admin.PolicyKey;
 import android.util.Log;
 
 import com.android.modules.utils.TypedXmlPullParser;
@@ -31,17 +33,18 @@
 final class IntegerPolicySerializer extends PolicySerializer<Integer> {
 
     @Override
-    void saveToXml(TypedXmlSerializer serializer, String attributeName, @NonNull Integer value)
-            throws IOException {
+    void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer, String attributeName,
+            @NonNull Integer value) throws IOException {
         Objects.requireNonNull(value);
         serializer.attributeInt(/* namespace= */ null, attributeName, value);
     }
 
     @Nullable
     @Override
-    Integer readFromXml(TypedXmlPullParser parser, String attributeName) {
+    IntegerPolicyValue readFromXml(TypedXmlPullParser parser, String attributeName) {
         try {
-            return parser.getAttributeInt(/* namespace= */ null, attributeName);
+            return new IntegerPolicyValue(
+                    parser.getAttributeInt(/* namespace= */ null, attributeName));
         } catch (XmlPullParserException e) {
             Log.e(DevicePolicyEngine.TAG, "Error parsing Integer policy value", e);
             return null;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/LockTaskPolicy.java b/services/devicepolicy/java/com/android/server/devicepolicy/LockTaskPolicy.java
deleted file mode 100644
index d3e8de4..0000000
--- a/services/devicepolicy/java/com/android/server/devicepolicy/LockTaskPolicy.java
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
- * 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.server.devicepolicy;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.admin.DevicePolicyManager;
-import android.util.Log;
-
-import com.android.modules.utils.TypedXmlPullParser;
-import com.android.modules.utils.TypedXmlSerializer;
-
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.IOException;
-import java.util.HashSet;
-import java.util.Objects;
-import java.util.Set;
-
-final class LockTaskPolicy {
-    static final int DEFAULT_LOCK_TASK_FLAG = DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS;
-    private Set<String> mPackages = new HashSet<>();
-    private int mFlags = DEFAULT_LOCK_TASK_FLAG;
-
-    @NonNull
-    Set<String> getPackages() {
-        return mPackages;
-    }
-
-    int getFlags() {
-        return mFlags;
-    }
-
-    LockTaskPolicy(Set<String> packages) {
-        Objects.requireNonNull(packages);
-        mPackages.addAll(packages);
-    }
-
-    private LockTaskPolicy(Set<String> packages, int flags) {
-        Objects.requireNonNull(packages);
-        mPackages = new HashSet<>(packages);
-        mFlags = flags;
-    }
-
-    void setPackages(@NonNull Set<String> packages) {
-        Objects.requireNonNull(packages);
-        mPackages = new HashSet<>(packages);
-    }
-
-    void setFlags(int flags) {
-        mFlags = flags;
-    }
-
-    @Override
-    public LockTaskPolicy clone() {
-        LockTaskPolicy policy = new LockTaskPolicy(mPackages);
-        policy.setFlags(mFlags);
-        return policy;
-    }
-
-    @Override
-    public boolean equals(@Nullable Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-        LockTaskPolicy other = (LockTaskPolicy) o;
-        return Objects.equals(mPackages, other.mPackages)
-                && mFlags == other.mFlags;
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(mPackages, mFlags);
-    }
-
-    @Override
-    public String toString() {
-        return "mPackages= " + String.join(", ", mPackages) + "; mFlags= " + mFlags;
-    }
-
-    static final class LockTaskPolicySerializer extends PolicySerializer<LockTaskPolicy> {
-
-        private static final String ATTR_PACKAGES = ":packages";
-        private static final String ATTR_PACKAGES_SEPARATOR = ";";
-        private static final String ATTR_FLAGS = ":flags";
-
-        @Override
-        void saveToXml(TypedXmlSerializer serializer, String attributeNamePrefix,
-                @NonNull LockTaskPolicy value) throws IOException {
-            Objects.requireNonNull(value);
-            if (value.mPackages == null || value.mPackages.isEmpty()) {
-                throw new IllegalArgumentException("Error saving LockTaskPolicy to file, lock task "
-                        + "packages must be present");
-            }
-            serializer.attribute(
-                    /* namespace= */ null,
-                    attributeNamePrefix + ATTR_PACKAGES,
-                    String.join(ATTR_PACKAGES_SEPARATOR, value.mPackages));
-            serializer.attributeInt(
-                    /* namespace= */ null,
-                    attributeNamePrefix + ATTR_FLAGS,
-                    value.mFlags);
-        }
-
-        @Override
-        LockTaskPolicy readFromXml(TypedXmlPullParser parser, String attributeNamePrefix) {
-            String packagesStr = parser.getAttributeValue(
-                    /* namespace= */ null,
-                    attributeNamePrefix + ATTR_PACKAGES);
-            if (packagesStr == null) {
-                Log.e(DevicePolicyEngine.TAG, "Error parsing LockTask policy value.");
-                return null;
-            }
-            Set<String> packages = Set.of(packagesStr.split(ATTR_PACKAGES_SEPARATOR));
-            try {
-                int flags = parser.getAttributeInt(
-                        /* namespace= */ null,
-                        attributeNamePrefix + ATTR_FLAGS);
-                return new LockTaskPolicy(packages, flags);
-            } catch (XmlPullParserException e) {
-                Log.e(DevicePolicyEngine.TAG, "Error parsing LockTask policy value", e);
-                return null;
-            }
-        }
-    }
-}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/LockTaskPolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/LockTaskPolicySerializer.java
new file mode 100644
index 0000000..3265b61
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/LockTaskPolicySerializer.java
@@ -0,0 +1,77 @@
+/*
+ * 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.devicepolicy;
+
+import android.annotation.NonNull;
+import android.app.admin.LockTaskPolicy;
+import android.app.admin.PolicyKey;
+import android.util.Log;
+
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.Objects;
+import java.util.Set;
+
+final class LockTaskPolicySerializer extends PolicySerializer<LockTaskPolicy> {
+
+    private static final String ATTR_PACKAGES = ":packages";
+    private static final String ATTR_PACKAGES_SEPARATOR = ";";
+    private static final String ATTR_FLAGS = ":flags";
+
+    @Override
+    void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer, String attributeNamePrefix,
+            @NonNull LockTaskPolicy value) throws IOException {
+        Objects.requireNonNull(value);
+        if (value.getPackages() == null || value.getPackages().isEmpty()) {
+            throw new IllegalArgumentException("Error saving LockTaskPolicy to file, lock task "
+                    + "packages must be present");
+        }
+        serializer.attribute(
+                /* namespace= */ null,
+                attributeNamePrefix + ATTR_PACKAGES,
+                String.join(ATTR_PACKAGES_SEPARATOR, value.getPackages()));
+        serializer.attributeInt(
+                /* namespace= */ null,
+                attributeNamePrefix + ATTR_FLAGS,
+                value.getFlags());
+    }
+
+    @Override
+    LockTaskPolicy readFromXml(TypedXmlPullParser parser, String attributeNamePrefix) {
+        String packagesStr = parser.getAttributeValue(
+                /* namespace= */ null,
+                attributeNamePrefix + ATTR_PACKAGES);
+        if (packagesStr == null) {
+            Log.e(DevicePolicyEngine.TAG, "Error parsing LockTask policy value.");
+            return null;
+        }
+        Set<String> packages = Set.of(packagesStr.split(ATTR_PACKAGES_SEPARATOR));
+        try {
+            int flags = parser.getAttributeInt(
+                    /* namespace= */ null,
+                    attributeNamePrefix + ATTR_FLAGS);
+            return new LockTaskPolicy(packages, flags);
+        } catch (XmlPullParserException e) {
+            Log.e(DevicePolicyEngine.TAG, "Error parsing LockTask policy value", e);
+            return null;
+        }
+    }
+}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/MostRecent.java b/services/devicepolicy/java/com/android/server/devicepolicy/MostRecent.java
new file mode 100644
index 0000000..423a497
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/MostRecent.java
@@ -0,0 +1,46 @@
+/*
+ * 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.server.devicepolicy;
+
+
+import android.annotation.NonNull;
+import android.app.admin.PolicyValue;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+final class MostRecent<V> extends ResolutionMechanism<V> {
+
+    @Override
+    PolicyValue<V> resolve(@NonNull LinkedHashMap<EnforcingAdmin, PolicyValue<V>> adminPolicies) {
+        List<Map.Entry<EnforcingAdmin, PolicyValue<V>>> policiesList = new ArrayList<>(
+                adminPolicies.entrySet());
+        return policiesList.isEmpty() ? null : policiesList.get(policiesList.size() - 1).getValue();
+    }
+
+    @Override
+    android.app.admin.MostRecent<V> getParcelableResolutionMechanism() {
+        return new android.app.admin.MostRecent<V>();
+    }
+
+    @Override
+    public String toString() {
+        return "MostRecent {}";
+    }
+}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/MostRestrictive.java b/services/devicepolicy/java/com/android/server/devicepolicy/MostRestrictive.java
index edb3d2e..7e8eaa7 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/MostRestrictive.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/MostRestrictive.java
@@ -17,6 +17,7 @@
 package com.android.server.devicepolicy;
 
 import android.annotation.NonNull;
+import android.app.admin.PolicyValue;
 
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -24,28 +25,34 @@
 
 final class MostRestrictive<V> extends ResolutionMechanism<V> {
 
-    private List<V> mMostToLeastRestrictive;
+    private List<PolicyValue<V>> mMostToLeastRestrictive;
 
-    MostRestrictive(@NonNull List<V> mostToLeastRestrictive) {
+    MostRestrictive(@NonNull List<PolicyValue<V>> mostToLeastRestrictive) {
         mMostToLeastRestrictive = mostToLeastRestrictive;
     }
 
     @Override
-    V resolve(@NonNull LinkedHashMap<EnforcingAdmin, V> adminPolicies) {
+    PolicyValue<V> resolve(@NonNull LinkedHashMap<EnforcingAdmin, PolicyValue<V>> adminPolicies) {
         if (adminPolicies.isEmpty()) {
             return null;
         }
-        for (V value : mMostToLeastRestrictive) {
+        for (PolicyValue<V> value : mMostToLeastRestrictive) {
             if (adminPolicies.containsValue(value)) {
                 return value;
             }
         }
         // Return first set policy if none can be found in known values
-        Map.Entry<EnforcingAdmin, V> policy = adminPolicies.entrySet().stream().findFirst().get();
+        Map.Entry<EnforcingAdmin, PolicyValue<V>> policy =
+                adminPolicies.entrySet().stream().findFirst().get();
         return policy.getValue();
     }
 
     @Override
+    android.app.admin.MostRestrictive<V> getParcelableResolutionMechanism() {
+        return new android.app.admin.MostRestrictive<V>(mMostToLeastRestrictive);
+    }
+
+    @Override
     public String toString() {
         return "MostRestrictive { mMostToLeastRestrictive= " + mMostToLeastRestrictive + " }";
     }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
index 581a199..3ca158d 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
@@ -579,6 +579,19 @@
         }
     }
 
+    void markMigrationToPolicyEngine() {
+        synchronized (mData) {
+            mData.mMigratedToPolicyEngine = true;
+            mData.writeDeviceOwner();
+        }
+    }
+
+    boolean isMigratedToPolicyEngine() {
+        synchronized (mData) {
+            return mData.mMigratedToPolicyEngine;
+        }
+    }
+
     @GuardedBy("mData")
     void pushToAppOpsLocked() {
         if (!mSystemReady) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
index 3040af2..63b250d 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
@@ -66,6 +66,7 @@
     private static final String TAG_DEVICE_OWNER_TYPE = "device-owner-type";
     private static final String TAG_DEVICE_OWNER_PROTECTED_PACKAGES =
             "device-owner-protected-packages";
+    private static final String TAG_POLICY_ENGINE_MIGRATION = "policy-engine-migration";
 
     private static final String ATTR_NAME = "name";
     private static final String ATTR_PACKAGE = "package";
@@ -84,6 +85,8 @@
             "isPoOrganizationOwnedDevice";
     private static final String ATTR_DEVICE_OWNER_TYPE_VALUE = "value";
 
+    private static final String ATTR_MIGRATED_TO_POLICY_ENGINE = "migratedToPolicyEngine";
+
     // Internal state for the device owner package.
     OwnerInfo mDeviceOwner;
     int mDeviceOwnerUserId = UserHandle.USER_NULL;
@@ -109,6 +112,8 @@
     SystemUpdateInfo mSystemUpdateInfo;
     private final PolicyPathProvider mPathProvider;
 
+    boolean mMigratedToPolicyEngine = false;
+
     OwnersData(PolicyPathProvider pathProvider) {
         mPathProvider = pathProvider;
     }
@@ -389,6 +394,11 @@
                 }
                 out.endTag(null, TAG_FREEZE_PERIOD_RECORD);
             }
+
+            out.startTag(null, TAG_POLICY_ENGINE_MIGRATION);
+            out.attributeBoolean(null, ATTR_MIGRATED_TO_POLICY_ENGINE, mMigratedToPolicyEngine);
+            out.endTag(null, TAG_POLICY_ENGINE_MIGRATION);
+
         }
 
         @Override
@@ -444,6 +454,9 @@
                     }
                     mDeviceOwnerProtectedPackages.put(packageName, protectedPackages);
                     break;
+                case TAG_POLICY_ENGINE_MIGRATION:
+                    mMigratedToPolicyEngine = parser.getAttributeBoolean(
+                            null, ATTR_MIGRATED_TO_POLICY_ENGINE, false);
                 default:
                     Slog.e(TAG, "Unexpected tag: " + tag);
                     return false;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PackageSpecificPolicyKey.java b/services/devicepolicy/java/com/android/server/devicepolicy/PackageSpecificPolicyKey.java
deleted file mode 100644
index 1665830..0000000
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PackageSpecificPolicyKey.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * 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.server.devicepolicy;
-
-import static android.app.admin.PolicyUpdatesReceiver.EXTRA_PACKAGE_NAME;
-import static android.app.admin.PolicyUpdatesReceiver.EXTRA_POLICY_BUNDLE_KEY;
-
-import android.annotation.Nullable;
-import android.os.Bundle;
-
-import com.android.modules.utils.TypedXmlPullParser;
-import com.android.modules.utils.TypedXmlSerializer;
-
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.IOException;
-import java.util.Objects;
-
-/**
- * Class used to identify a policy that relates to a certain package in the policy engine's data
- * structure.
- */
-final class PackageSpecificPolicyKey extends PolicyKey {
-    private static final String ATTR_POLICY_KEY = "policy-key";
-    private static final String ATTR_PACKAGE_NAME = "package-name";
-    private static final String ATTR_PERMISSION_NAME = "permission-name";
-
-    private final String mPackageName;
-
-    PackageSpecificPolicyKey(String key, String packageName) {
-        super(key);
-        mPackageName = Objects.requireNonNull((packageName));
-    }
-
-    PackageSpecificPolicyKey(String key) {
-        super(key);
-        mPackageName = null;
-    }
-
-    @Nullable
-    String getPackageName() {
-        return mPackageName;
-    }
-
-    @Override
-    void saveToXml(TypedXmlSerializer serializer) throws IOException {
-        serializer.attribute(/* namespace= */ null, ATTR_POLICY_KEY, mKey);
-        serializer.attribute(/* namespace= */ null, ATTR_PACKAGE_NAME, mPackageName);
-    }
-
-    @Override
-    PackageSpecificPolicyKey readFromXml(TypedXmlPullParser parser)
-            throws XmlPullParserException, IOException {
-        String policyKey = parser.getAttributeValue(/* namespace= */ null, ATTR_POLICY_KEY);
-        String packageName = parser.getAttributeValue(/* namespace= */ null, ATTR_PACKAGE_NAME);
-        String permissionName = parser.getAttributeValue(
-                /* namespace= */ null, ATTR_PERMISSION_NAME);
-        return new PackageSpecificPolicyKey(policyKey, packageName);
-    }
-
-    @Override
-    void writeToBundle(Bundle bundle) {
-        super.writeToBundle(bundle);
-        Bundle extraPolicyParams = new Bundle();
-        extraPolicyParams.putString(EXTRA_PACKAGE_NAME, mPackageName);
-        bundle.putBundle(EXTRA_POLICY_BUNDLE_KEY, extraPolicyParams);
-    }
-
-    @Override
-    public boolean equals(@Nullable Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-        PackageSpecificPolicyKey other = (PackageSpecificPolicyKey) o;
-        return Objects.equals(mKey, other.mKey)
-                && Objects.equals(mPackageName, other.mPackageName);
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(mKey, mPackageName);
-    }
-
-    @Override
-    public String toString() {
-        return "mPolicyKey= " + mKey + "; mPackageName= " + mPackageName;
-    }
-}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PermissionGrantStatePolicyKey.java b/services/devicepolicy/java/com/android/server/devicepolicy/PermissionGrantStatePolicyKey.java
deleted file mode 100644
index b7d805e..0000000
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PermissionGrantStatePolicyKey.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * 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.server.devicepolicy;
-
-import static android.app.admin.PolicyUpdatesReceiver.EXTRA_PACKAGE_NAME;
-import static android.app.admin.PolicyUpdatesReceiver.EXTRA_PERMISSION_NAME;
-import static android.app.admin.PolicyUpdatesReceiver.EXTRA_POLICY_BUNDLE_KEY;
-
-import android.annotation.Nullable;
-import android.os.Bundle;
-
-import com.android.modules.utils.TypedXmlPullParser;
-import com.android.modules.utils.TypedXmlSerializer;
-
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.IOException;
-import java.util.Objects;
-
-/**
- * Class used to identify a PermissionGrantState policy in the policy engine's data structure.
- */
-final class PermissionGrantStatePolicyKey extends PolicyKey {
-    private static final String ATTR_POLICY_KEY = "policy-key";
-    private static final String ATTR_PACKAGE_NAME = "package-name";
-    private static final String ATTR_PERMISSION_NAME = "permission-name";
-
-    private final String mPackageName;
-    private final String mPermissionName;
-
-    PermissionGrantStatePolicyKey(String key, String packageName, String permissionName) {
-        super(key);
-        mPackageName = Objects.requireNonNull((packageName));
-        mPermissionName = Objects.requireNonNull((permissionName));
-    }
-
-    PermissionGrantStatePolicyKey(String key) {
-        super(key);
-        mPackageName = null;
-        mPermissionName = null;
-    }
-
-    @Nullable
-    String getPackageName() {
-        return mPackageName;
-    }
-
-    @Nullable
-    String getPermissionName() {
-        return mPermissionName;
-    }
-
-    @Override
-    void saveToXml(TypedXmlSerializer serializer) throws IOException {
-        serializer.attribute(/* namespace= */ null, ATTR_POLICY_KEY, mKey);
-        serializer.attribute(/* namespace= */ null, ATTR_PACKAGE_NAME, mPackageName);
-        serializer.attribute(/* namespace= */ null, ATTR_PERMISSION_NAME, mPermissionName);
-    }
-
-    @Override
-    PermissionGrantStatePolicyKey readFromXml(TypedXmlPullParser parser)
-            throws XmlPullParserException, IOException {
-        String policyKey = parser.getAttributeValue(/* namespace= */ null, ATTR_POLICY_KEY);
-        String packageName = parser.getAttributeValue(/* namespace= */ null, ATTR_PACKAGE_NAME);
-        String permissionName = parser.getAttributeValue(
-                /* namespace= */ null, ATTR_PERMISSION_NAME);
-        return new PermissionGrantStatePolicyKey(policyKey, packageName, permissionName);
-    }
-
-    @Override
-    void writeToBundle(Bundle bundle) {
-        super.writeToBundle(bundle);
-        Bundle extraPolicyParams = new Bundle();
-        extraPolicyParams.putString(EXTRA_PACKAGE_NAME, mPackageName);
-        extraPolicyParams.putString(EXTRA_PERMISSION_NAME, mPermissionName);
-        bundle.putBundle(EXTRA_POLICY_BUNDLE_KEY, extraPolicyParams);
-    }
-
-    @Override
-    public boolean equals(@Nullable Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-        PermissionGrantStatePolicyKey other = (PermissionGrantStatePolicyKey) o;
-        return Objects.equals(mKey, other.mKey)
-                && Objects.equals(mPackageName, other.mPackageName)
-                && Objects.equals(mPermissionName, other.mPermissionName);
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(mKey, mPackageName, mPermissionName);
-    }
-
-    @Override
-    public String toString() {
-        return "mPolicyKey= " + mKey + "; mPackageName= " + mPackageName + "; mPermissionName= "
-                + mPermissionName;
-    }
-}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PersistentPreferredActivityPolicyKey.java b/services/devicepolicy/java/com/android/server/devicepolicy/PersistentPreferredActivityPolicyKey.java
deleted file mode 100644
index f8c07595..0000000
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PersistentPreferredActivityPolicyKey.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * 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.server.devicepolicy;
-
-import static android.app.admin.PolicyUpdatesReceiver.EXTRA_INTENT_FILTER;
-import static android.app.admin.PolicyUpdatesReceiver.EXTRA_POLICY_BUNDLE_KEY;
-
-import android.annotation.Nullable;
-import android.content.IntentFilter;
-import android.os.Bundle;
-
-import com.android.modules.utils.TypedXmlPullParser;
-import com.android.modules.utils.TypedXmlSerializer;
-import com.android.server.IntentResolver;
-
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.IOException;
-import java.util.Objects;
-
-/**
- * Class used to identify a PersistentPreferredActivity policy in the policy engine's data
- * structure.
- */
-final class PersistentPreferredActivityPolicyKey extends PolicyKey {
-    private static final String ATTR_POLICY_KEY = "policy-key";
-    private IntentFilter mFilter;
-
-    PersistentPreferredActivityPolicyKey(String policyKey, IntentFilter filter) {
-        super(policyKey);
-        mFilter = Objects.requireNonNull((filter));
-    }
-
-    PersistentPreferredActivityPolicyKey(String policyKey) {
-        super(policyKey);
-        mFilter = null;
-    }
-
-    @Nullable
-    IntentFilter getFilter() {
-        return mFilter;
-    }
-
-    @Override
-    void saveToXml(TypedXmlSerializer serializer) throws IOException {
-        serializer.attribute(/* namespace= */ null, ATTR_POLICY_KEY, mKey);
-        mFilter.writeToXml(serializer);
-    }
-
-    @Override
-    PersistentPreferredActivityPolicyKey readFromXml(TypedXmlPullParser parser)
-            throws XmlPullParserException, IOException {
-        String policyKey = parser.getAttributeValue(/* namespace= */ null, ATTR_POLICY_KEY);
-        IntentFilter filter = new IntentFilter();
-        filter.readFromXml(parser);
-        return new PersistentPreferredActivityPolicyKey(policyKey, filter);
-    }
-
-    @Override
-    void writeToBundle(Bundle bundle) {
-        super.writeToBundle(bundle);
-        Bundle extraPolicyParams = new Bundle();
-        extraPolicyParams.putParcelable(EXTRA_INTENT_FILTER, mFilter);
-        bundle.putBundle(EXTRA_POLICY_BUNDLE_KEY, extraPolicyParams);
-    }
-
-    @Override
-    public boolean equals(@Nullable Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-        PersistentPreferredActivityPolicyKey other = (PersistentPreferredActivityPolicyKey) o;
-        return Objects.equals(mKey, other.mKey)
-                && IntentResolver.filterEquals(mFilter, other.mFilter);
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(mKey, mFilter);
-    }
-
-    @Override
-    public String toString() {
-        return "mKey= " + mKey + "; mFilter= " + mFilter;
-    }
-}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
index ab1658f..ab6f732 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -18,10 +18,20 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.admin.BooleanPolicyValue;
 import android.app.admin.DevicePolicyManager;
+import android.app.admin.IntegerPolicyValue;
+import android.app.admin.IntentFilterPolicyKey;
+import android.app.admin.LockTaskPolicy;
+import android.app.admin.NoArgsPolicyKey;
+import android.app.admin.PackagePermissionPolicyKey;
+import android.app.admin.PackagePolicyKey;
+import android.app.admin.PolicyKey;
+import android.app.admin.PolicyValue;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.IntentFilter;
+import android.os.Bundle;
 
 import com.android.internal.util.function.QuadFunction;
 import com.android.modules.utils.TypedXmlPullParser;
@@ -44,35 +54,50 @@
     // Only use this flag if a policy can not be applied globally.
     private static final int POLICY_FLAG_LOCAL_ONLY_POLICY = 1 << 1;
 
+    // Only use this flag if a policy is inheritable by child profile from parent.
+    private static final int POLICY_FLAG_INHERITABLE = 1 << 2;
+
+    // Use this flag if admin policies should be treated independently of each other and should not
+    // have any resolution logic applied, this should only be used for very limited policies were
+    // this would make sense and the enforcing logic should handle it appropriately, e.g.
+    // application restrictions set by different admins for a single package should not be merged,
+    // but saved and queried independent of each other.
+    // Currently, support is  added for local only policies, if you need to add a non coexistable
+    // global policy please add support.
+    private static final int POLICY_FLAG_NON_COEXISTABLE_POLICY = 1 << 3;
+
     private static final MostRestrictive<Boolean> FALSE_MORE_RESTRICTIVE = new MostRestrictive<>(
-            List.of(false, true));
+            List.of(new BooleanPolicyValue(false), new BooleanPolicyValue(true)));
 
     private static final MostRestrictive<Boolean> TRUE_MORE_RESTRICTIVE = new MostRestrictive<>(
-            List.of(true, false));
+            List.of(new BooleanPolicyValue(true), new BooleanPolicyValue(false)));
 
     static PolicyDefinition<Boolean> AUTO_TIMEZONE = new PolicyDefinition<>(
-            new DefaultPolicyKey(DevicePolicyManager.AUTO_TIMEZONE_POLICY),
-            // auto timezone is enabled by default, hence disabling it is more restrictive.
-            FALSE_MORE_RESTRICTIVE,
+            new NoArgsPolicyKey(DevicePolicyManager.AUTO_TIMEZONE_POLICY),
+            // auto timezone is disabled by default, hence enabling it is more restrictive.
+            TRUE_MORE_RESTRICTIVE,
             POLICY_FLAG_GLOBAL_ONLY_POLICY,
             (Boolean value, Context context, Integer userId, PolicyKey policyKey) ->
                     PolicyEnforcerCallbacks.setAutoTimezoneEnabled(value, context),
             new BooleanPolicySerializer());
 
     // This is saved in the static map sPolicyDefinitions so that we're able to reconstruct the
-    // actual permission grant policy with the correct arguments (packageName and permission name)
+    // actual policy with the correct arguments (packageName and permission name)
     // when reading the policies from xml.
     static final PolicyDefinition<Integer> GENERIC_PERMISSION_GRANT =
             new PolicyDefinition<>(
-                    new PermissionGrantStatePolicyKey(
-                            DevicePolicyManager.PERMISSION_GRANT_POLICY_KEY),
+                    new PackagePermissionPolicyKey(DevicePolicyManager.PERMISSION_GRANT_POLICY),
                     // TODO: is this really the best mechanism, what makes denied more
                     //  restrictive than
                     //  granted?
                     new MostRestrictive<>(
-                            List.of(DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED,
-                                    DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED,
-                                    DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT)),
+                            List.of(
+                                    new IntegerPolicyValue(
+                                            DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED),
+                                    new IntegerPolicyValue(
+                                            DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED),
+                                    new IntegerPolicyValue(
+                                            DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT))),
                     POLICY_FLAG_LOCAL_ONLY_POLICY,
                     PolicyEnforcerCallbacks::setPermissionGrantState,
                     new IntegerPolicySerializer());
@@ -87,14 +112,14 @@
             return GENERIC_PERMISSION_GRANT;
         }
         return GENERIC_PERMISSION_GRANT.createPolicyDefinition(
-                new PermissionGrantStatePolicyKey(
-                        DevicePolicyManager.PERMISSION_GRANT_POLICY_KEY,
+                new PackagePermissionPolicyKey(
+                        DevicePolicyManager.PERMISSION_GRANT_POLICY,
                         packageName,
                         permissionName));
     }
 
     static PolicyDefinition<LockTaskPolicy> LOCK_TASK = new PolicyDefinition<>(
-            new DefaultPolicyKey(DevicePolicyManager.LOCK_TASK_POLICY),
+            new NoArgsPolicyKey(DevicePolicyManager.LOCK_TASK_POLICY),
             new TopPriority<>(List.of(
                     // TODO(b/258166155): add correct device lock role name
                     EnforcingAdmin.getRoleAuthorityOf("DeviceLock"),
@@ -102,21 +127,22 @@
             POLICY_FLAG_LOCAL_ONLY_POLICY,
             (LockTaskPolicy value, Context context, Integer userId, PolicyKey policyKey) ->
                     PolicyEnforcerCallbacks.setLockTask(value, context, userId),
-            new LockTaskPolicy.LockTaskPolicySerializer());
+            new LockTaskPolicySerializer());
 
-    static PolicyDefinition<Set<String>> USER_CONTROLLED_DISABLED_PACKAGES = new PolicyDefinition<>(
-            new DefaultPolicyKey(DevicePolicyManager.USER_CONTROL_DISABLED_PACKAGES_POLICY),
-            new SetUnion<>(),
-            (Set<String> value, Context context, Integer userId, PolicyKey policyKey) ->
-                    PolicyEnforcerCallbacks.setUserControlDisabledPackages(value, userId),
-            new SetPolicySerializer<>());
+    static PolicyDefinition<Set<String>> USER_CONTROLLED_DISABLED_PACKAGES =
+            new PolicyDefinition<>(
+                    new NoArgsPolicyKey(DevicePolicyManager.USER_CONTROL_DISABLED_PACKAGES_POLICY),
+                    new StringSetUnion(),
+                    (Set<String> value, Context context, Integer userId, PolicyKey policyKey) ->
+                            PolicyEnforcerCallbacks.setUserControlDisabledPackages(value, userId),
+                    new StringSetPolicySerializer());
 
     // This is saved in the static map sPolicyDefinitions so that we're able to reconstruct the
-    // actual permission grant policy with the correct arguments (packageName and permission name)
-    // when reading the policies from xml.
+    // actual policy with the correct arguments (i.e. packageName) when reading the policies from
+    // xml.
     static PolicyDefinition<ComponentName> GENERIC_PERSISTENT_PREFERRED_ACTIVITY =
             new PolicyDefinition<>(
-                    new PersistentPreferredActivityPolicyKey(
+                    new IntentFilterPolicyKey(
                             DevicePolicyManager.PERSISTENT_PREFERRED_ACTIVITY_POLICY),
             new TopPriority<>(List.of(
                     // TODO(b/258166155): add correct device lock role name
@@ -136,16 +162,16 @@
             return GENERIC_PERSISTENT_PREFERRED_ACTIVITY;
         }
         return GENERIC_PERSISTENT_PREFERRED_ACTIVITY.createPolicyDefinition(
-                new PersistentPreferredActivityPolicyKey(
+                new IntentFilterPolicyKey(
                         DevicePolicyManager.PERSISTENT_PREFERRED_ACTIVITY_POLICY, intentFilter));
     }
 
     // This is saved in the static map sPolicyDefinitions so that we're able to reconstruct the
-    // actual uninstall blocked policy with the correct arguments (i.e. packageName)
-    // when reading the policies from xml.
+    // actual policy with the correct arguments (i.e. packageName) when reading the policies from
+    // xml.
     static PolicyDefinition<Boolean> GENERIC_PACKAGE_UNINSTALL_BLOCKED =
             new PolicyDefinition<>(
-                    new PackageSpecificPolicyKey(
+                    new PackagePolicyKey(
                             DevicePolicyManager.PACKAGE_UNINSTALL_BLOCKED_POLICY),
                     TRUE_MORE_RESTRICTIVE,
                     POLICY_FLAG_LOCAL_ONLY_POLICY,
@@ -162,19 +188,49 @@
             return GENERIC_PACKAGE_UNINSTALL_BLOCKED;
         }
         return GENERIC_PACKAGE_UNINSTALL_BLOCKED.createPolicyDefinition(
-                new PackageSpecificPolicyKey(
+                new PackagePolicyKey(
                         DevicePolicyManager.PACKAGE_UNINSTALL_BLOCKED_POLICY, packageName));
     }
 
+    // This is saved in the static map sPolicyDefinitions so that we're able to reconstruct the
+    // actual policy with the correct arguments (i.e. packageName) when reading the policies from
+    // xml.
+    static PolicyDefinition<Bundle> GENERIC_APPLICATION_RESTRICTIONS =
+            new PolicyDefinition<>(
+                    new PackagePolicyKey(
+                            DevicePolicyManager.APPLICATION_RESTRICTIONS_POLICY),
+                    new MostRecent<>(),
+                    POLICY_FLAG_LOCAL_ONLY_POLICY | POLICY_FLAG_NON_COEXISTABLE_POLICY,
+                    // Application restrictions are now stored and retrieved from DPMS, so no
+                    // enforcing is required, however DPMS calls into UM to set restrictions for
+                    // backwards compatibility.
+                    (Bundle value, Context context, Integer userId, PolicyKey policyKey) -> true,
+                    new BundlePolicySerializer());
+
+    /**
+     * Passing in {@code null} for {@code packageName} will return
+     * {@link #GENERIC_APPLICATION_RESTRICTIONS}.
+     */
+    static PolicyDefinition<Bundle> APPLICATION_RESTRICTIONS(
+            String packageName) {
+        if (packageName == null) {
+            return GENERIC_APPLICATION_RESTRICTIONS;
+        }
+        return GENERIC_APPLICATION_RESTRICTIONS.createPolicyDefinition(
+                new PackagePolicyKey(
+                        DevicePolicyManager.APPLICATION_RESTRICTIONS_POLICY, packageName));
+    }
+
     private static final Map<String, PolicyDefinition<?>> sPolicyDefinitions = Map.of(
             DevicePolicyManager.AUTO_TIMEZONE_POLICY, AUTO_TIMEZONE,
-            DevicePolicyManager.PERMISSION_GRANT_POLICY_KEY, GENERIC_PERMISSION_GRANT,
+            DevicePolicyManager.PERMISSION_GRANT_POLICY, GENERIC_PERMISSION_GRANT,
             DevicePolicyManager.LOCK_TASK_POLICY, LOCK_TASK,
             DevicePolicyManager.USER_CONTROL_DISABLED_PACKAGES_POLICY,
             USER_CONTROLLED_DISABLED_PACKAGES,
             DevicePolicyManager.PERSISTENT_PREFERRED_ACTIVITY_POLICY,
             GENERIC_PERSISTENT_PREFERRED_ACTIVITY,
-            DevicePolicyManager.PACKAGE_UNINSTALL_BLOCKED_POLICY, GENERIC_PACKAGE_UNINSTALL_BLOCKED
+            DevicePolicyManager.PACKAGE_UNINSTALL_BLOCKED_POLICY, GENERIC_PACKAGE_UNINSTALL_BLOCKED,
+            DevicePolicyManager.APPLICATION_RESTRICTIONS_POLICY, GENERIC_APPLICATION_RESTRICTIONS
     );
 
 
@@ -196,6 +252,10 @@
         return mPolicyKey;
     }
 
+    @NonNull
+    ResolutionMechanism<V> getResolutionMechanism() {
+        return mResolutionMechanism;
+    }
     /**
      * Returns {@code true} if the policy is a global policy by nature and can't be applied locally.
      */
@@ -210,8 +270,23 @@
         return (mPolicyFlags & POLICY_FLAG_LOCAL_ONLY_POLICY) != 0;
     }
 
+    /**
+     * Returns {@code true} if the policy is inheritable by child profiles.
+     */
+    boolean isInheritable() {
+        return (mPolicyFlags & POLICY_FLAG_INHERITABLE) != 0;
+    }
+
+    /**
+     * Returns {@code true} if the policy engine should not try to resolve policies set by different
+     * admins and should just store it and pass it on to the enforcing logic.
+     */
+    boolean isNonCoexistablePolicy() {
+        return (mPolicyFlags & POLICY_FLAG_NON_COEXISTABLE_POLICY) != 0;
+    }
+
     @Nullable
-    V resolvePolicy(LinkedHashMap<EnforcingAdmin, V> adminsPolicy) {
+    PolicyValue<V> resolvePolicy(LinkedHashMap<EnforcingAdmin, PolicyValue<V>> adminsPolicy) {
         return mResolutionMechanism.resolve(adminsPolicy);
     }
 
@@ -247,6 +322,10 @@
         mPolicyEnforcerCallback = policyEnforcerCallback;
         mPolicySerializer = policySerializer;
 
+        if (isNonCoexistablePolicy() && !isLocalOnlyPolicy()) {
+            throw new UnsupportedOperationException("Non-coexistable global policies not supported,"
+                    + "please add support.");
+        }
         // TODO: maybe use this instead of manually adding to the map
 //        sPolicyDefinitions.put(policyDefinitionKey, this);
     }
@@ -261,26 +340,27 @@
         // TODO: can we avoid casting?
         PolicyKey policyKey = readPolicyKeyFromXml(parser);
         PolicyDefinition<V> genericPolicyDefinition =
-                (PolicyDefinition<V>) sPolicyDefinitions.get(policyKey.mKey);
+                (PolicyDefinition<V>) sPolicyDefinitions.get(policyKey.getIdentifier());
         return genericPolicyDefinition.createPolicyDefinition(policyKey);
     }
 
     static <V> PolicyKey readPolicyKeyFromXml(TypedXmlPullParser parser)
             throws XmlPullParserException, IOException {
         // TODO: can we avoid casting?
-        PolicyKey policyKey = DefaultPolicyKey.readGenericPolicyKeyFromXml(parser);
-        PolicyDefinition<V> genericPolicyDefinition =
-                (PolicyDefinition<V>) sPolicyDefinitions.get(policyKey.mKey);
+        PolicyKey policyKey = PolicyKey.readGenericPolicyKeyFromXml(parser);
+        PolicyDefinition<PolicyValue<V>> genericPolicyDefinition =
+                (PolicyDefinition<PolicyValue<V>>) sPolicyDefinitions.get(
+                        policyKey.getIdentifier());
         return genericPolicyDefinition.mPolicyKey.readFromXml(parser);
     }
 
     void savePolicyValueToXml(TypedXmlSerializer serializer, String attributeName, V value)
             throws IOException {
-        mPolicySerializer.saveToXml(serializer, attributeName, value);
+        mPolicySerializer.saveToXml(mPolicyKey, serializer, attributeName, value);
     }
 
     @Nullable
-    V readPolicyValueFromXml(TypedXmlPullParser parser, String attributeName) {
+    PolicyValue<V> readPolicyValueFromXml(TypedXmlPullParser parser, String attributeName) {
         return mPolicySerializer.readFromXml(parser, attributeName);
     }
 
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
index e2aa23d..4ae7ca6 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
@@ -20,6 +20,11 @@
 import android.annotation.Nullable;
 import android.app.AppGlobals;
 import android.app.admin.DevicePolicyManager;
+import android.app.admin.IntentFilterPolicyKey;
+import android.app.admin.LockTaskPolicy;
+import android.app.admin.PackagePermissionPolicyKey;
+import android.app.admin.PackagePolicyKey;
+import android.app.admin.PolicyKey;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.IntentFilter;
@@ -64,11 +69,11 @@
             @Nullable Integer grantState, @NonNull Context context, int userId,
             @NonNull PolicyKey policyKey) {
         return Boolean.TRUE.equals(Binder.withCleanCallingIdentity(() -> {
-            if (!(policyKey instanceof PermissionGrantStatePolicyKey)) {
+            if (!(policyKey instanceof PackagePermissionPolicyKey)) {
                 throw new IllegalArgumentException("policyKey is not of type "
                         + "PermissionGrantStatePolicyKey");
             }
-            PermissionGrantStatePolicyKey parsedKey = (PermissionGrantStatePolicyKey) policyKey;
+            PackagePermissionPolicyKey parsedKey = (PackagePermissionPolicyKey) policyKey;
             Objects.requireNonNull(parsedKey.getPermissionName());
             Objects.requireNonNull(parsedKey.getPackageName());
             Objects.requireNonNull(context);
@@ -156,13 +161,13 @@
             @NonNull PolicyKey policyKey) {
         Binder.withCleanCallingIdentity(() -> {
             try {
-                if (!(policyKey instanceof PersistentPreferredActivityPolicyKey)) {
+                if (!(policyKey instanceof IntentFilterPolicyKey)) {
                     throw new IllegalArgumentException("policyKey is not of type "
-                            + "PersistentPreferredActivityPolicyKey");
+                            + "IntentFilterPolicyKey");
                 }
-                PersistentPreferredActivityPolicyKey parsedKey =
-                        (PersistentPreferredActivityPolicyKey) policyKey;
-                IntentFilter filter = Objects.requireNonNull(parsedKey.getFilter());
+                IntentFilterPolicyKey parsedKey =
+                        (IntentFilterPolicyKey) policyKey;
+                IntentFilter filter = Objects.requireNonNull(parsedKey.getIntentFilter());
 
                 IPackageManager packageManager = AppGlobals.getPackageManager();
                 if (preferredActivity != null) {
@@ -184,11 +189,11 @@
             @Nullable Boolean uninstallBlocked, @NonNull Context context, int userId,
             @NonNull PolicyKey policyKey) {
         return Boolean.TRUE.equals(Binder.withCleanCallingIdentity(() -> {
-            if (!(policyKey instanceof PackageSpecificPolicyKey)) {
+            if (!(policyKey instanceof PackagePolicyKey)) {
                 throw new IllegalArgumentException("policyKey is not of type "
-                        + "PackageSpecificPolicyKey");
+                        + "PackagePolicyKey");
             }
-            PackageSpecificPolicyKey parsedKey = (PackageSpecificPolicyKey) policyKey;
+            PackagePolicyKey parsedKey = (PackagePolicyKey) policyKey;
             String packageName = Objects.requireNonNull(parsedKey.getPackageName());
             DevicePolicyManagerService.setUninstallBlockedUnchecked(
                     packageName,
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyKey.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyKey.java
deleted file mode 100644
index 571f0ee..0000000
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyKey.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * 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.server.devicepolicy;
-
-import static android.app.admin.PolicyUpdatesReceiver.EXTRA_POLICY_KEY;
-
-import android.annotation.Nullable;
-import android.os.Bundle;
-
-import com.android.modules.utils.TypedXmlPullParser;
-import com.android.modules.utils.TypedXmlSerializer;
-
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.IOException;
-import java.util.Objects;
-
-/**
- * Abstract class used to identify a policy in the policy engine's data structure.
- */
-abstract class PolicyKey {
-    private static final String ATTR_GENERIC_POLICY_KEY = "generic-policy-key";
-
-    protected final String mKey;
-
-    PolicyKey(String policyKey) {
-        mKey = Objects.requireNonNull(policyKey);
-    }
-
-    String getKey() {
-        return mKey;
-    }
-
-    boolean hasSameKeyAs(PolicyKey other) {
-        if (other == null) {
-            return false;
-        }
-        return mKey.equals(other.mKey);
-    }
-
-    void saveToXml(TypedXmlSerializer serializer) throws IOException {
-        serializer.attribute(/* namespace= */ null, ATTR_GENERIC_POLICY_KEY, mKey);
-    }
-
-    PolicyKey readFromXml(TypedXmlPullParser parser)
-            throws XmlPullParserException, IOException {
-        // No need to read anything
-        return this;
-    }
-
-    void writeToBundle(Bundle bundle) {
-        bundle.putString(EXTRA_POLICY_KEY, mKey);
-    }
-
-    @Override
-    public boolean equals(@Nullable Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-        PolicyKey other = (PolicyKey) o;
-        return Objects.equals(mKey, other.mKey);
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(mKey);
-    }
-}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicySerializer.java
index 528d3b0..0ef431f 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicySerializer.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicySerializer.java
@@ -17,6 +17,8 @@
 package com.android.server.devicepolicy;
 
 import android.annotation.NonNull;
+import android.app.admin.PolicyKey;
+import android.app.admin.PolicyValue;
 
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
@@ -24,7 +26,8 @@
 import java.io.IOException;
 
 abstract class PolicySerializer<V> {
-    abstract void saveToXml(TypedXmlSerializer serializer, String attributeName, @NonNull V value)
+    abstract void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer,
+            String attributeName, @NonNull V value)
             throws IOException;
-    abstract V readFromXml(TypedXmlPullParser parser, String attributeName);
+    abstract PolicyValue<V> readFromXml(TypedXmlPullParser parser, String attributeName);
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
index db0a623..3a792d8 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.admin.PolicyValue;
 import android.util.Log;
 
 import com.android.internal.util.XmlUtils;
@@ -40,8 +41,9 @@
     private static final String ATTR_RESOLVED_POLICY = "resolved-policy";
 
     private final PolicyDefinition<V> mPolicyDefinition;
-    private final LinkedHashMap<EnforcingAdmin, V> mPoliciesSetByAdmins = new LinkedHashMap<>();
-    private V mCurrentResolvedPolicy;
+    private final LinkedHashMap<EnforcingAdmin, PolicyValue<V>> mPoliciesSetByAdmins =
+            new LinkedHashMap<>();
+    private PolicyValue<V> mCurrentResolvedPolicy;
 
     PolicyState(@NonNull PolicyDefinition<V> policyDefinition) {
         mPolicyDefinition = Objects.requireNonNull(policyDefinition);
@@ -49,8 +51,8 @@
 
     private PolicyState(
             @NonNull PolicyDefinition<V> policyDefinition,
-            @NonNull LinkedHashMap<EnforcingAdmin, V> policiesSetByAdmins,
-            V currentEnforcedPolicy) {
+            @NonNull LinkedHashMap<EnforcingAdmin, PolicyValue<V>> policiesSetByAdmins,
+            PolicyValue<V> currentEnforcedPolicy) {
         Objects.requireNonNull(policyDefinition);
         Objects.requireNonNull(policiesSetByAdmins);
 
@@ -62,8 +64,14 @@
     /**
      * Returns {@code true} if the resolved policy has changed, {@code false} otherwise.
      */
-    boolean addPolicy(@NonNull EnforcingAdmin admin, @NonNull V policy) {
-        mPoliciesSetByAdmins.put(Objects.requireNonNull(admin), Objects.requireNonNull(policy));
+    boolean addPolicy(@NonNull EnforcingAdmin admin, @NonNull PolicyValue<V> policy) {
+        Objects.requireNonNull(admin);
+        Objects.requireNonNull(policy);
+
+        //LinkedHashMap doesn't update the insertion order of existing keys, removing the existing
+        // key will cause it to update.
+        mPoliciesSetByAdmins.remove(admin);
+        mPoliciesSetByAdmins.put(admin, policy);
 
         return resolvePolicy();
     }
@@ -78,8 +86,8 @@
      * Returns {@code true} if the resolved policy has changed, {@code false} otherwise.
      */
     boolean addPolicy(
-            @NonNull EnforcingAdmin admin, @NonNull V policy,
-            LinkedHashMap<EnforcingAdmin, V> globalPoliciesSetByAdmins) {
+            @NonNull EnforcingAdmin admin, @NonNull PolicyValue<V> policy,
+            LinkedHashMap<EnforcingAdmin, PolicyValue<V>> globalPoliciesSetByAdmins) {
         mPoliciesSetByAdmins.put(Objects.requireNonNull(admin), Objects.requireNonNull(policy));
 
         return resolvePolicy(globalPoliciesSetByAdmins);
@@ -109,7 +117,7 @@
      */
     boolean removePolicy(
             @NonNull EnforcingAdmin admin,
-            LinkedHashMap<EnforcingAdmin, V> globalPoliciesSetByAdmins) {
+            LinkedHashMap<EnforcingAdmin, PolicyValue<V>> globalPoliciesSetByAdmins) {
         Objects.requireNonNull(admin);
 
         if (mPoliciesSetByAdmins.remove(admin) == null) {
@@ -128,13 +136,17 @@
      *
      * Returns {@code true} if the resolved policy has changed, {@code false} otherwise.
      */
-    boolean resolvePolicy(LinkedHashMap<EnforcingAdmin, V> globalPoliciesSetByAdmins) {
+    boolean resolvePolicy(LinkedHashMap<EnforcingAdmin, PolicyValue<V>> globalPoliciesSetByAdmins) {
+        //Non coexistable policies don't need resolving
+        if (mPolicyDefinition.isNonCoexistablePolicy()) {
+            return false;
+        }
         // Add global policies first then override with local policies for the same admin.
-        LinkedHashMap<EnforcingAdmin, V> mergedPolicies =
+        LinkedHashMap<EnforcingAdmin, PolicyValue<V>> mergedPolicies =
                 new LinkedHashMap<>(globalPoliciesSetByAdmins);
         mergedPolicies.putAll(mPoliciesSetByAdmins);
 
-        V resolvedPolicy = mPolicyDefinition.resolvePolicy(mergedPolicies);
+        PolicyValue<V> resolvedPolicy = mPolicyDefinition.resolvePolicy(mergedPolicies);
         boolean policyChanged = !Objects.equals(resolvedPolicy, mCurrentResolvedPolicy);
         mCurrentResolvedPolicy = resolvedPolicy;
 
@@ -142,12 +154,16 @@
     }
 
     @NonNull
-    LinkedHashMap<EnforcingAdmin, V> getPoliciesSetByAdmins() {
+    LinkedHashMap<EnforcingAdmin, PolicyValue<V>> getPoliciesSetByAdmins() {
         return new LinkedHashMap<>(mPoliciesSetByAdmins);
     }
 
     private boolean resolvePolicy() {
-        V resolvedPolicy = mPolicyDefinition.resolvePolicy(mPoliciesSetByAdmins);
+        //Non coexistable policies don't need resolving
+        if (mPolicyDefinition.isNonCoexistablePolicy()) {
+            return false;
+        }
+        PolicyValue<V> resolvedPolicy = mPolicyDefinition.resolvePolicy(mPoliciesSetByAdmins);
         boolean policyChanged = !Objects.equals(resolvedPolicy, mCurrentResolvedPolicy);
         mCurrentResolvedPolicy = resolvedPolicy;
 
@@ -155,10 +171,20 @@
     }
 
     @Nullable
-    V getCurrentResolvedPolicy() {
+    PolicyValue<V> getCurrentResolvedPolicy() {
         return mCurrentResolvedPolicy;
     }
 
+    android.app.admin.PolicyState<V> getParcelablePolicyState() {
+        LinkedHashMap<android.app.admin.EnforcingAdmin, PolicyValue<V>> adminPolicies =
+                new LinkedHashMap<>();
+        for (EnforcingAdmin admin : mPoliciesSetByAdmins.keySet()) {
+            adminPolicies.put(admin.getParcelableAdmin(), mPoliciesSetByAdmins.get(admin));
+        }
+        return new android.app.admin.PolicyState<>(adminPolicies, mCurrentResolvedPolicy,
+                mPolicyDefinition.getResolutionMechanism().getParcelableResolutionMechanism());
+    }
+
     @Override
     public String toString() {
         return "PolicyState { mPolicyDefinition= " + mPolicyDefinition + ", mPoliciesSetByAdmins= "
@@ -171,14 +197,14 @@
 
         if (mCurrentResolvedPolicy != null) {
             mPolicyDefinition.savePolicyValueToXml(
-                    serializer, ATTR_RESOLVED_POLICY, mCurrentResolvedPolicy);
+                    serializer, ATTR_RESOLVED_POLICY, mCurrentResolvedPolicy.getValue());
         }
 
         for (EnforcingAdmin admin : mPoliciesSetByAdmins.keySet()) {
             serializer.startTag(/* namespace= */ null, TAG_ADMIN_POLICY_ENTRY);
 
             mPolicyDefinition.savePolicyValueToXml(
-                    serializer, ATTR_POLICY_VALUE, mPoliciesSetByAdmins.get(admin));
+                    serializer, ATTR_POLICY_VALUE, mPoliciesSetByAdmins.get(admin).getValue());
 
             serializer.startTag(/* namespace= */ null, TAG_ENFORCING_ADMIN_ENTRY);
             admin.saveToXml(serializer);
@@ -193,15 +219,15 @@
 
         PolicyDefinition<V> policyDefinition = PolicyDefinition.readFromXml(parser);
 
-        V currentResolvedPolicy = policyDefinition.readPolicyValueFromXml(
+        PolicyValue<V> currentResolvedPolicy = policyDefinition.readPolicyValueFromXml(
                 parser, ATTR_RESOLVED_POLICY);
 
-        LinkedHashMap<EnforcingAdmin, V> policiesSetByAdmins = new LinkedHashMap<>();
+        LinkedHashMap<EnforcingAdmin, PolicyValue<V>> policiesSetByAdmins = new LinkedHashMap<>();
         int outerDepth = parser.getDepth();
         while (XmlUtils.nextElementWithin(parser, outerDepth)) {
             String tag = parser.getName();
             if (TAG_ADMIN_POLICY_ENTRY.equals(tag)) {
-                V value = policyDefinition.readPolicyValueFromXml(
+                PolicyValue<V> value = policyDefinition.readPolicyValueFromXml(
                         parser, ATTR_POLICY_VALUE);
                 EnforcingAdmin admin;
                 int adminPolicyDepth = parser.getDepth();
@@ -216,4 +242,8 @@
         }
         return new PolicyState<V>(policyDefinition, policiesSetByAdmins, currentResolvedPolicy);
     }
+
+    PolicyDefinition<V> getPolicyDefinition() {
+        return mPolicyDefinition;
+    }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/ResolutionMechanism.java b/services/devicepolicy/java/com/android/server/devicepolicy/ResolutionMechanism.java
index 7b720bc..c321aa1 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/ResolutionMechanism.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/ResolutionMechanism.java
@@ -17,10 +17,12 @@
 package com.android.server.devicepolicy;
 
 import android.annotation.Nullable;
+import android.app.admin.PolicyValue;
 
 import java.util.LinkedHashMap;
 
 abstract class ResolutionMechanism<V> {
     @Nullable
-    abstract V resolve(LinkedHashMap<EnforcingAdmin, V> adminPolicies);
+    abstract PolicyValue<V> resolve(LinkedHashMap<EnforcingAdmin, PolicyValue<V>> adminPolicies);
+    abstract android.app.admin.ResolutionMechanism<V> getParcelableResolutionMechanism();
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/SetPolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/SetPolicySerializer.java
deleted file mode 100644
index 736627b..0000000
--- a/services/devicepolicy/java/com/android/server/devicepolicy/SetPolicySerializer.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * 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.server.devicepolicy;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-
-import com.android.modules.utils.TypedXmlPullParser;
-import com.android.modules.utils.TypedXmlSerializer;
-
-import java.io.IOException;
-import java.util.Objects;
-import java.util.Set;
-
-// TODO(scottjonathan): Replace with actual implementation
-final class SetPolicySerializer<V> extends PolicySerializer<Set<V>> {
-
-    @Override
-    void saveToXml(TypedXmlSerializer serializer, String attributeName, @NonNull Set<V> value)
-            throws IOException {
-        Objects.requireNonNull(value);
-    }
-
-    @Nullable
-    @Override
-    Set<V> readFromXml(TypedXmlPullParser parser, String attributeName) {
-        return null;
-    }
-}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/StringSetPolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/StringSetPolicySerializer.java
new file mode 100644
index 0000000..dc6592d
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/StringSetPolicySerializer.java
@@ -0,0 +1,61 @@
+/*
+ * 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.server.devicepolicy;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.admin.PolicyKey;
+import android.app.admin.PolicyValue;
+import android.app.admin.StringSetPolicyValue;
+import android.util.Log;
+
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import java.io.IOException;
+import java.util.Objects;
+import java.util.Set;
+
+// TODO(scottjonathan): Replace with generic set implementation
+final class StringSetPolicySerializer extends PolicySerializer<Set<String>> {
+    private static final String ATTR_VALUES = ":strings";
+    private static final String ATTR_VALUES_SEPARATOR = ";";
+
+    @Override
+    void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer, String attributeNamePrefix,
+            @NonNull Set<String> value) throws IOException {
+        Objects.requireNonNull(value);
+        serializer.attribute(
+                /* namespace= */ null,
+                attributeNamePrefix + ATTR_VALUES,
+                String.join(ATTR_VALUES_SEPARATOR, value));
+    }
+
+    @Nullable
+    @Override
+    PolicyValue<Set<String>> readFromXml(TypedXmlPullParser parser, String attributeNamePrefix) {
+        String valuesStr = parser.getAttributeValue(
+                /* namespace= */ null,
+                attributeNamePrefix + ATTR_VALUES);
+        if (valuesStr == null) {
+            Log.e(DevicePolicyEngine.TAG, "Error parsing StringSet policy value.");
+            return null;
+        }
+        Set<String> values = Set.of(valuesStr.split(ATTR_VALUES_SEPARATOR));
+        return new StringSetPolicyValue(values);
+    }
+}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/SetUnion.java b/services/devicepolicy/java/com/android/server/devicepolicy/StringSetUnion.java
similarity index 60%
rename from services/devicepolicy/java/com/android/server/devicepolicy/SetUnion.java
rename to services/devicepolicy/java/com/android/server/devicepolicy/StringSetUnion.java
index cf26983..5298960 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/SetUnion.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/StringSetUnion.java
@@ -17,28 +17,37 @@
 package com.android.server.devicepolicy;
 
 import android.annotation.NonNull;
+import android.app.admin.PolicyValue;
+import android.app.admin.StringSetPolicyValue;
 
 import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.Objects;
 import java.util.Set;
 
-final class SetUnion<V> extends ResolutionMechanism<Set<V>> {
+final class StringSetUnion extends ResolutionMechanism<Set<String>> {
 
     @Override
-    Set<V> resolve(@NonNull LinkedHashMap<EnforcingAdmin, Set<V>> adminPolicies) {
+    PolicyValue<Set<String>> resolve(
+            @NonNull LinkedHashMap<EnforcingAdmin, PolicyValue<Set<String>>> adminPolicies) {
         Objects.requireNonNull(adminPolicies);
         if (adminPolicies.isEmpty()) {
             return null;
         }
-        Set<V> unionOfPolicies = new HashSet<>();
-        for (Set<V> policy : adminPolicies.values()) {
-            unionOfPolicies.addAll(policy);
+        Set<String> unionOfPolicies = new HashSet<>();
+        for (PolicyValue<Set<String>> policy : adminPolicies.values()) {
+            unionOfPolicies.addAll(policy.getValue());
         }
-        return unionOfPolicies;
+        return new StringSetPolicyValue(unionOfPolicies);
     }
 
     @Override
+    android.app.admin.StringSetUnion getParcelableResolutionMechanism() {
+        return new android.app.admin.StringSetUnion();
+    }
+
+
+    @Override
     public String toString() {
         return "SetUnion {}";
     }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/TopPriority.java b/services/devicepolicy/java/com/android/server/devicepolicy/TopPriority.java
index 571cf64..839840b 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/TopPriority.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/TopPriority.java
@@ -17,6 +17,7 @@
 package com.android.server.devicepolicy;
 
 import android.annotation.NonNull;
+import android.app.admin.PolicyValue;
 
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -34,7 +35,7 @@
     }
 
     @Override
-    V resolve(@NonNull LinkedHashMap<EnforcingAdmin, V> adminPolicies) {
+    PolicyValue<V> resolve(@NonNull LinkedHashMap<EnforcingAdmin, PolicyValue<V>> adminPolicies) {
         if (adminPolicies.isEmpty()) {
             return null;
         }
@@ -46,11 +47,17 @@
             }
         }
         // Return first set policy if no known authority is found
-        Map.Entry<EnforcingAdmin, V> policy = adminPolicies.entrySet().stream().findFirst().get();
+        Map.Entry<EnforcingAdmin, PolicyValue<V>> policy =
+                adminPolicies.entrySet().stream().findFirst().get();
         return policy.getValue();
     }
 
     @Override
+    android.app.admin.TopPriority<V> getParcelableResolutionMechanism() {
+        return new android.app.admin.TopPriority<>(mHighestToLowestPriorityAuthorities);
+    }
+
+    @Override
     public String toString() {
         return "TopPriority { mHighestToLowestPriorityAuthorities= "
                 + mHighestToLowestPriorityAuthorities + " }";
diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp
index 9c9b363..81a5472 100644
--- a/services/incremental/IncrementalService.cpp
+++ b/services/incremental/IncrementalService.cpp
@@ -419,6 +419,17 @@
     return nowUs - monoTsUs;
 }
 
+static const char* loadingStateToString(incfs::LoadingState state) {
+    switch (state) {
+        case (incfs::LoadingState::Full):
+            return "Full";
+        case (incfs::LoadingState::MissingBlocks):
+            return "MissingBlocks";
+        default:
+            return "error obtaining loading state";
+    }
+}
+
 void IncrementalService::onDump(int fd) {
     dprintf(fd, "Incremental is %s\n", incfs::enabled() ? "ENABLED" : "DISABLED");
     dprintf(fd, "IncFs features: 0x%x\n", int(mIncFs->features()));
@@ -453,9 +464,13 @@
             }
             dprintf(fd, "    storages (%d): {\n", int(mnt.storages.size()));
             for (auto&& [storageId, storage] : mnt.storages) {
-                dprintf(fd, "      [%d] -> [%s] (%d %% loaded) \n", storageId, storage.name.c_str(),
+                auto&& ifs = getIfsLocked(storageId);
+                dprintf(fd, "      [%d] -> [%s] (%d %% loaded)(%s) \n", storageId,
+                        storage.name.c_str(),
                         (int)(getLoadingProgressFromPath(mnt, storage.name.c_str()).getProgress() *
-                              100));
+                              100),
+                        ifs ? loadingStateToString(mIncFs->isEverythingFullyLoaded(ifs->control))
+                            : "error obtaining ifs");
             }
             dprintf(fd, "    }\n");
 
@@ -2816,6 +2831,12 @@
 
 binder::Status IncrementalService::DataLoaderStub::onStatusChanged(MountId mountId, int newStatus) {
     if (!isValid()) {
+        if (newStatus == IDataLoaderStatusListener::DATA_LOADER_BOUND) {
+            // Async "bound" came to already destroyed stub.
+            // Unbind immediately to avoid invalid stub sitting around in DataLoaderManagerService.
+            mService.mDataLoaderManager->unbindFromDataLoader(mountId);
+            return binder::Status::ok();
+        }
         return binder::Status::
                 fromServiceSpecificError(-EINVAL, "onStatusChange came to invalid DataLoaderStub");
     }
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index cae6c39..b117cae 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -142,6 +142,7 @@
 import com.android.server.lights.LightsService;
 import com.android.server.locales.LocaleManagerService;
 import com.android.server.location.LocationManagerService;
+import com.android.server.location.altitude.AltitudeService;
 import com.android.server.logcat.LogcatManagerService;
 import com.android.server.media.MediaRouterService;
 import com.android.server.media.metrics.MediaMetricsManagerService;
@@ -1792,6 +1793,10 @@
         }
         t.traceEnd();
 
+        t.traceBegin("ArtManagerLocal");
+        DexOptHelper.initializeArtManagerLocal(context, mPackageManagerService);
+        t.traceEnd();
+
         t.traceBegin("UpdatePackagesIfNeeded");
         try {
             Watchdog.getInstance().pauseWatchingCurrentThread("dexopt");
@@ -2119,6 +2124,14 @@
             }
             t.traceEnd();
 
+            t.traceBegin("StartAltitudeService");
+            try {
+                mSystemServiceManager.startService(AltitudeService.Lifecycle.class);
+            } catch (Throwable e) {
+                reportWtf("starting AltitudeService service", e);
+            }
+            t.traceEnd();
+
             t.traceBegin("StartLocationTimeZoneManagerService");
             try {
                 mSystemServiceManager.startService(LOCATION_TIME_ZONE_MANAGER_SERVICE_CLASS);
@@ -2757,10 +2770,6 @@
         mSystemServiceManager.startService(PermissionPolicyService.class);
         t.traceEnd();
 
-        t.traceBegin("ArtManagerLocal");
-        DexOptHelper.initializeArtManagerLocal(context, mPackageManagerService);
-        t.traceEnd();
-
         t.traceBegin("MakePackageManagerServiceReady");
         mPackageManagerService.systemReady();
         t.traceEnd();
diff --git a/services/manifest_services.xml b/services/manifest_services.xml
new file mode 100644
index 0000000..7638915
--- /dev/null
+++ b/services/manifest_services.xml
@@ -0,0 +1,7 @@
+<manifest version="1.0" type="framework">
+    <hal format="aidl">
+        <name>android.frameworks.location.altitude</name>
+        <version>1</version>
+        <fqname>IAltitudeService/default</fqname>
+    </hal>
+</manifest>
diff --git a/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt b/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt
index a26b2ac..af85eba 100644
--- a/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt
@@ -27,6 +27,7 @@
 import android.os.UserHandle
 import android.util.SparseBooleanArray
 import android.util.SparseIntArray
+import com.android.internal.annotations.VisibleForTesting
 import com.android.internal.util.ArrayUtils
 import com.android.internal.util.function.pooled.PooledLambda
 import com.android.server.appop.AppOpsCheckingServiceInterface
@@ -67,6 +68,24 @@
         }
     }
 
+    @VisibleForTesting
+    override fun writeState() {
+        // TODO Not yet implemented
+    }
+
+    override fun readState() {
+        // TODO Not yet implemented
+    }
+
+    @VisibleForTesting
+    override fun shutdown() {
+        // TODO Not yet implemented
+    }
+
+    override fun systemReady() {
+        // TODO Not yet implemented
+    }
+
     override fun getNonDefaultUidModes(uid: Int): SparseIntArray {
         return opNameMapToOpIntMap(getUidModes(uid))
     }
diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
index c7e9371..6eaa411 100644
--- a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
@@ -2023,6 +2023,8 @@
      */
     private inner class OnPermissionFlagsChangedListener :
         UidPermissionPolicy.OnPermissionFlagsChangedListener() {
+        private var isPermissionFlagsChanged = false
+
         private val runtimePermissionChangedUids = IntSet()
         // Mapping from UID to whether only notifications permissions are revoked.
         private val runtimePermissionRevokedUids = IntBooleanMap()
@@ -2046,6 +2048,8 @@
             oldFlags: Int,
             newFlags: Int
         ) {
+            isPermissionFlagsChanged = true
+
             val uid = UserHandle.getUid(userId, appId)
             val permission = service.getState {
                 with(policy) { getPermissions()[permissionName] }
@@ -2072,6 +2076,11 @@
         }
 
         override fun onStateMutated() {
+            if (isPermissionFlagsChanged) {
+                PackageManager.invalidatePackageInfoCache()
+                isPermissionFlagsChanged = false
+            }
+
             runtimePermissionChangedUids.forEachIndexed { _, uid ->
                 onPermissionsChangeListeners.onPermissionsChanged(uid)
             }
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/AppChildProcessTest.java b/services/tests/mockingservicestests/src/com/android/server/am/AppChildProcessTest.java
index 961fc18..1c0989c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/AppChildProcessTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/AppChildProcessTest.java
@@ -247,7 +247,8 @@
         }
 
         @Override
-        public AppOpsService getAppOpsService(File file, Handler handler) {
+        public AppOpsService getAppOpsService(File recentAccessesFile, File storageFile,
+                Handler handler) {
             return mAppOpsService;
         }
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
index 9c581f9..9263bff 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
@@ -1078,7 +1078,8 @@
         }
 
         @Override
-        public AppOpsService getAppOpsService(File file, Handler handler) {
+        public AppOpsService getAppOpsService(File recentAccessesFile, File storageFile,
+                Handler handler) {
             return mAppOpsService;
         }
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java b/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
index ea14ffb..7c5d96e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
@@ -159,7 +159,8 @@
         }
 
         @Override
-        public AppOpsService getAppOpsService(File file, Handler handler) {
+        public AppOpsService getAppOpsService(File recentAccessesFile, File storageFile,
+                Handler handler) {
             return mAppOpsService;
         }
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index 5e4ba88..458c9cfc 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -372,7 +372,8 @@
         }
 
         @Override
-        public AppOpsService getAppOpsService(File file, Handler handler) {
+        public AppOpsService getAppOpsService(File recentAccessesFile, File storageFile,
+                Handler handler) {
             return mAppOpsService;
         }
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/CacheOomRankerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/CacheOomRankerTest.java
index 766ba72..434d200 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/CacheOomRankerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/CacheOomRankerTest.java
@@ -793,7 +793,8 @@
         }
 
         @Override
-        public AppOpsService getAppOpsService(File file, Handler handler) {
+        public AppOpsService getAppOpsService(File recentAccessesFile, File storageFile,
+                Handler handler) {
             return mAppOpsService;
         }
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
index f0cd6cc..3fd0e07 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
@@ -1160,7 +1160,8 @@
         }
 
         @Override
-        public AppOpsService getAppOpsService(File file, Handler handler) {
+        public AppOpsService getAppOpsService(File recentAccessesFile, File storageFile,
+                Handler handler) {
             return mAppOpsService;
         }
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java
index 52027e7..f86e464 100644
--- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java
@@ -57,7 +57,6 @@
 import com.android.server.LocalServices;
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.pm.pkg.PackageStateInternal;
-import com.android.server.testutils.MockitoUtilsKt;
 
 import org.junit.After;
 import org.junit.Before;
@@ -80,12 +79,14 @@
 
     private static final String TAG = AppOpsServiceTest.class.getSimpleName();
     // State will be persisted into this XML file.
-    private static final String APP_OPS_FILENAME = "appops-service-test.xml";
+    private static final String APP_OPS_FILENAME = "appops.test.xml";
+    private static final String APP_OPS_ACCESSES_FILENAME = "appops_accesses.test.xml";
 
     private static final Context sContext = InstrumentationRegistry.getTargetContext();
     private static final String sMyPackageName = sContext.getOpPackageName();
 
-    private File mAppOpsFile;
+    private File mStorageFile;
+    private File mRecentAccessesFile;
     private Handler mHandler;
     private AppOpsService mAppOpsService;
     private int mMyUid;
@@ -93,7 +94,8 @@
     private StaticMockitoSession mMockingSession;
 
     private void setupAppOpsService() {
-        mAppOpsService = new AppOpsService(mAppOpsFile, mHandler, spy(sContext));
+        mAppOpsService = new AppOpsService(mRecentAccessesFile, mStorageFile, mHandler,
+                spy(sContext));
         mAppOpsService.mHistoricalRegistry.systemReady(sContext.getContentResolver());
 
         // Always approve all permission checks
@@ -103,11 +105,10 @@
 
     @Before
     public void setUp() {
-        mAppOpsFile = new File(sContext.getFilesDir(), APP_OPS_FILENAME);
-        if (mAppOpsFile.exists()) {
-            // Start with a clean state (persisted into XML).
-            mAppOpsFile.delete();
-        }
+        mStorageFile = new File(sContext.getFilesDir(), APP_OPS_FILENAME);
+        mRecentAccessesFile = new File(sContext.getFilesDir(), APP_OPS_ACCESSES_FILENAME);
+        mStorageFile.delete();
+        mRecentAccessesFile.delete();
 
         HandlerThread handlerThread = new HandlerThread(TAG);
         handlerThread.start();
@@ -212,10 +213,12 @@
         mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false);
         mAppOpsService.noteOperation(OP_WRITE_SMS, mMyUid, sMyPackageName, null, false, null,
                 false);
-        mAppOpsService.writeState();
+
+        mAppOpsService.shutdown();
 
         // Create a new app ops service which will initialize its state from XML.
         setupAppOpsService();
+        mAppOpsService.readState();
 
         // Query the state of the 2nd service.
         List<PackageOps> loggedOps = getLoggedOps();
diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java
index d1f7f93..5474c20 100644
--- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java
@@ -17,6 +17,7 @@
 package com.android.server.appop;
 
 import static android.app.AppOpsManager.OP_SCHEDULE_EXACT_ALARM;
+import static android.app.AppOpsManager._NUM_OP;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -39,20 +40,19 @@
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
+import android.content.pm.UserPackage;
 import android.content.res.AssetManager;
 import android.os.Handler;
 import android.os.UserHandle;
 import android.util.ArrayMap;
 import android.util.Log;
 import android.util.SparseArray;
-import android.util.SparseIntArray;
 import android.util.Xml;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.dx.mockito.inline.extended.ExtendedMockito;
 import com.android.internal.util.ArrayUtils;
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.server.LocalServices;
@@ -88,7 +88,6 @@
     private static final String APP_OPS_VERSION_1_ASSET_PATH =
             "AppOpsUpgradeTest/appops-version-1.xml";
     private static final String APP_OPS_FILENAME = "appops-test.xml";
-    private static final int NON_DEFAULT_OPS_IN_FILE = 4;
 
     private static final Context sContext = InstrumentationRegistry.getTargetContext();
     private static final File sAppOpsFile = new File(sContext.getFilesDir(), APP_OPS_FILENAME);
@@ -107,6 +106,9 @@
     @Mock
     private Handler mHandler;
 
+    private Object mLock = new Object();
+    private SparseArray<int[]> mSwitchedOps;
+
     private static void extractAppOpsFile(String assetPath) {
         sAppOpsFile.getParentFile().mkdirs();
         try (FileOutputStream out = new FileOutputStream(sAppOpsFile);
@@ -124,42 +126,6 @@
         }
     }
 
-    private void assertSameModes(SparseArray<AppOpsService.UidState> uidStates,
-            int op1, int op2) {
-        int numberOfNonDefaultOps = 0;
-        final int defaultModeOp1 = AppOpsManager.opToDefaultMode(op1);
-        final int defaultModeOp2 = AppOpsManager.opToDefaultMode(op2);
-        for (int i = 0; i < uidStates.size(); i++) {
-            final AppOpsService.UidState uidState = uidStates.valueAt(i);
-            SparseIntArray opModes = uidState.getNonDefaultUidModes();
-            if (opModes != null) {
-                final int uidMode1 = opModes.get(op1, defaultModeOp1);
-                final int uidMode2 = opModes.get(op2, defaultModeOp2);
-                assertEquals(uidMode1, uidMode2);
-                if (uidMode1 != defaultModeOp1) {
-                    numberOfNonDefaultOps++;
-                }
-            }
-            if (uidState.pkgOps == null) {
-                continue;
-            }
-            for (int j = 0; j < uidState.pkgOps.size(); j++) {
-                final AppOpsService.Ops ops = uidState.pkgOps.valueAt(j);
-                if (ops == null) {
-                    continue;
-                }
-                final AppOpsService.Op _op1 = ops.get(op1);
-                final AppOpsService.Op _op2 = ops.get(op2);
-                final int mode1 = (_op1 == null) ? defaultModeOp1 : _op1.getMode();
-                final int mode2 = (_op2 == null) ? defaultModeOp2 : _op2.getMode();
-                assertEquals(mode1, mode2);
-                if (mode1 != defaultModeOp1) {
-                    numberOfNonDefaultOps++;
-                }
-            }
-        }
-        assertEquals(numberOfNonDefaultOps, NON_DEFAULT_OPS_IN_FILE);
-    }
 
     @Before
     public void setUp() {
@@ -196,6 +162,19 @@
                 .getPackageStates();
 
         doReturn(new int[] {0}).when(mUserManagerInternal).getUserIds();
+
+        // Build mSwitchedOps
+        mSwitchedOps = buildSwitchedOpsArray();
+    }
+
+    private SparseArray<int[]> buildSwitchedOpsArray() {
+        SparseArray<int[]> switchedOps = new SparseArray<>();
+        for (int switchedCode = 0; switchedCode < _NUM_OP; switchedCode++) {
+            int switchCode = AppOpsManager.opToSwitch(switchedCode);
+            switchedOps.put(switchCode,
+                    ArrayUtils.appendInt(switchedOps.get(switchCode), switchedCode));
+        }
+        return switchedOps;
     }
 
     @After
@@ -207,13 +186,31 @@
     public void upgradeRunAnyInBackground() {
         extractAppOpsFile(APP_OPS_UNVERSIONED_ASSET_PATH);
 
-        AppOpsService testService = new AppOpsService(sAppOpsFile, mHandler, mTestContext);
+        AppOpsCheckingServiceImpl testService = new AppOpsCheckingServiceImpl(sAppOpsFile, mLock,
+                mHandler, mTestContext, mSwitchedOps);
+        testService.readState();
 
         testService.upgradeRunAnyInBackgroundLocked();
-        assertSameModes(testService.mUidStates, AppOpsManager.OP_RUN_IN_BACKGROUND,
+
+        assertSameModes(testService, AppOpsManager.OP_RUN_IN_BACKGROUND,
                 AppOpsManager.OP_RUN_ANY_IN_BACKGROUND);
     }
 
+    private void assertSameModes(AppOpsCheckingServiceImpl testService, int op1, int op2) {
+        for (int uid : testService.getUidsWithNonDefaultModes()) {
+            assertEquals(
+                    testService.getUidMode(uid, op1),
+                    testService.getUidMode(uid, op2)
+            );
+        }
+        for (UserPackage pkg : testService.getPackagesWithNonDefaultModes()) {
+            assertEquals(
+                    testService.getPackageMode(pkg.packageName, op1, pkg.userId),
+                    testService.getPackageMode(pkg.packageName, op2, pkg.userId)
+            );
+        }
+    }
+
     private static int getModeInFile(int uid) {
         switch (uid) {
             case 10198:
@@ -252,7 +249,9 @@
             return UserHandle.getUid(userId, appIds[index]);
         }).when(mPackageManagerInternal).getPackageUid(anyString(), anyLong(), anyInt());
 
-        AppOpsService testService = new AppOpsService(sAppOpsFile, mHandler, mTestContext);
+        AppOpsCheckingServiceImpl testService = new AppOpsCheckingServiceImpl(sAppOpsFile, mLock,
+                mHandler, mTestContext, mSwitchedOps);
+        testService.readState();
 
         testService.upgradeScheduleExactAlarmLocked();
 
@@ -267,8 +266,8 @@
                 } else {
                     expectedMode = previousMode;
                 }
-                final AppOpsService.UidState uidState = testService.mUidStates.get(uid);
-                assertEquals(expectedMode, uidState.getUidMode(OP_SCHEDULE_EXACT_ALARM));
+                int mode = testService.getUidMode(uid, OP_SCHEDULE_EXACT_ALARM);
+                assertEquals(expectedMode, mode);
             }
         }
 
@@ -276,9 +275,8 @@
         int[] unrelatedUidsInFile = {10225, 10178};
 
         for (int uid : unrelatedUidsInFile) {
-            final AppOpsService.UidState uidState = testService.mUidStates.get(uid);
-            assertEquals(AppOpsManager.opToDefaultMode(OP_SCHEDULE_EXACT_ALARM),
-                    uidState.getUidMode(OP_SCHEDULE_EXACT_ALARM));
+            int mode = testService.getUidMode(uid, OP_SCHEDULE_EXACT_ALARM);
+            assertEquals(AppOpsManager.opToDefaultMode(OP_SCHEDULE_EXACT_ALARM), mode);
         }
     }
 
@@ -286,8 +284,9 @@
     public void upgradeFromNoFile() {
         assertFalse(sAppOpsFile.exists());
 
-        AppOpsService testService = spy(
-                new AppOpsService(sAppOpsFile, mHandler, mTestContext));
+        AppOpsCheckingServiceImpl testService = spy(new AppOpsCheckingServiceImpl(sAppOpsFile,
+                mLock, mHandler, mTestContext, mSwitchedOps));
+        testService.readState();
 
         doNothing().when(testService).upgradeRunAnyInBackgroundLocked();
         doNothing().when(testService).upgradeScheduleExactAlarmLocked();
@@ -304,7 +303,7 @@
 
         AppOpsDataParser parser = new AppOpsDataParser(sAppOpsFile);
         assertTrue(parser.parse());
-        assertEquals(AppOpsService.CURRENT_VERSION, parser.mVersion);
+        assertEquals(AppOpsCheckingServiceImpl.CURRENT_VERSION, parser.mVersion);
     }
 
     @Test
@@ -314,8 +313,9 @@
         assertTrue(parser.parse());
         assertEquals(AppOpsDataParser.NO_VERSION, parser.mVersion);
 
-        AppOpsService testService = spy(
-                new AppOpsService(sAppOpsFile, mHandler, mTestContext));
+        AppOpsCheckingServiceImpl testService = spy(new AppOpsCheckingServiceImpl(sAppOpsFile,
+                mLock, mHandler, mTestContext, mSwitchedOps));
+        testService.readState();
 
         doNothing().when(testService).upgradeRunAnyInBackgroundLocked();
         doNothing().when(testService).upgradeScheduleExactAlarmLocked();
@@ -328,7 +328,7 @@
 
         testService.writeState();
         assertTrue(parser.parse());
-        assertEquals(AppOpsService.CURRENT_VERSION, parser.mVersion);
+        assertEquals(AppOpsCheckingServiceImpl.CURRENT_VERSION, parser.mVersion);
     }
 
     @Test
@@ -338,8 +338,9 @@
         assertTrue(parser.parse());
         assertEquals(1, parser.mVersion);
 
-        AppOpsService testService = spy(
-                new AppOpsService(sAppOpsFile, mHandler, mTestContext));
+        AppOpsCheckingServiceImpl testService = spy(new AppOpsCheckingServiceImpl(sAppOpsFile,
+                mLock, mHandler, mTestContext, mSwitchedOps));
+        testService.readState();
 
         doNothing().when(testService).upgradeRunAnyInBackgroundLocked();
         doNothing().when(testService).upgradeScheduleExactAlarmLocked();
@@ -352,7 +353,7 @@
 
         testService.writeState();
         assertTrue(parser.parse());
-        assertEquals(AppOpsService.CURRENT_VERSION, parser.mVersion);
+        assertEquals(AppOpsCheckingServiceImpl.CURRENT_VERSION, parser.mVersion);
     }
 
     /**
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 c7bacbc..63a5ff1 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
@@ -211,12 +211,12 @@
                 jobInfoBuilder.build(), callingUid, sourcePkg, 0, "JSSTest", testTag);
     }
 
-    private void grantRunLongJobsPermission(boolean grant) {
+    private void grantRunUserInitiatedJobsPermission(boolean grant) {
         final int permissionStatus = grant
                 ? PermissionChecker.PERMISSION_GRANTED : PermissionChecker.PERMISSION_HARD_DENIED;
         doReturn(permissionStatus)
                 .when(() -> PermissionChecker.checkPermissionForPreflight(
-                        any(), eq(android.Manifest.permission.RUN_LONG_JOBS),
+                        any(), eq(android.Manifest.permission.RUN_USER_INITIATED_JOBS),
                         anyInt(), anyInt(), anyString()));
     }
 
@@ -284,10 +284,10 @@
                 mService.getMinJobExecutionGuaranteeMs(jobHigh));
         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
                 mService.getMinJobExecutionGuaranteeMs(jobDef));
-        grantRunLongJobsPermission(false); // Without permission
+        grantRunUserInitiatedJobsPermission(false); // Without permission
         assertEquals(mService.mConstants.RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS,
                 mService.getMinJobExecutionGuaranteeMs(jobDT));
-        grantRunLongJobsPermission(true); // With permission
+        grantRunUserInitiatedJobsPermission(true); // With permission
         doReturn(ConnectivityController.UNKNOWN_TIME)
                 .when(connectivityController).getEstimatedTransferTimeMs(any());
         assertEquals(mService.mConstants.RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS,
@@ -305,14 +305,14 @@
         assertEquals(mService.mConstants.RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS,
                 mService.getMinJobExecutionGuaranteeMs(jobDT));
         // UserInitiated
-        grantRunLongJobsPermission(false);
+        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));
-        grantRunLongJobsPermission(true); // With permission
+        grantRunUserInitiatedJobsPermission(true); // With permission
         assertEquals(mService.mConstants.RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS,
                 mService.getMinJobExecutionGuaranteeMs(jobUI));
         doReturn(ConnectivityController.UNKNOWN_TIME)
@@ -364,14 +364,14 @@
         doReturn(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS)
                 .when(quotaController).getMaxJobExecutionTimeMsLocked(any());
 
-        grantRunLongJobsPermission(true);
+        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));
-        grantRunLongJobsPermission(false);
+        grantRunUserInitiatedJobsPermission(false);
         assertEquals(mService.mConstants.RUNTIME_DATA_TRANSFER_LIMIT_MS,
                 mService.getMaxJobExecutionTimeMs(jobDT));
         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorVisibleBackgroundUserTestCase.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorVisibleBackgroundUserTestCase.java
index 49c6a88..af85ef4 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorVisibleBackgroundUserTestCase.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorVisibleBackgroundUserTestCase.java
@@ -20,6 +20,7 @@
 import static android.view.Display.INVALID_DISPLAY;
 
 import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_FAILURE;
+import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_ALREADY_VISIBLE;
 import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE;
 import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE;
 import static com.android.server.pm.UserVisibilityChangedEvent.onInvisible;
@@ -234,6 +235,38 @@
     }
 
     @Test
+    public final void testStartVisibleBgUser_onSecondaryDisplay_displayAlreadyAssignedToSameUser()
+            throws Exception {
+        AsyncUserVisibilityListener listener = addListenerForEvents(onVisible(USER_ID));
+        startUserInSecondaryDisplay(USER_ID, SECONDARY_DISPLAY_ID);
+
+        expectUserIsVisible(USER_ID);
+        expectUserIsVisibleOnDisplay(USER_ID, SECONDARY_DISPLAY_ID);
+        expectUserIsNotVisibleOnDisplay(USER_ID, INVALID_DISPLAY);
+        expectUserIsNotVisibleOnDisplay(USER_ID, DEFAULT_DISPLAY);
+        expectVisibleUsers(INITIAL_CURRENT_USER_ID, USER_ID);
+        expectDisplayAssignedToUser(USER_ID, SECONDARY_DISPLAY_ID);
+        expectUserAssignedToDisplay(SECONDARY_DISPLAY_ID, USER_ID);
+        assertUserCanBeAssignedExtraDisplay(USER_ID, OTHER_SECONDARY_DISPLAY_ID);
+
+        int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG_VISIBLE,
+                SECONDARY_DISPLAY_ID);
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_ALREADY_VISIBLE);
+
+        // Run same assertions above
+        expectUserIsVisible(USER_ID);
+        expectUserIsVisibleOnDisplay(USER_ID, SECONDARY_DISPLAY_ID);
+        expectUserIsNotVisibleOnDisplay(USER_ID, INVALID_DISPLAY);
+        expectUserIsNotVisibleOnDisplay(USER_ID, DEFAULT_DISPLAY);
+        expectVisibleUsers(INITIAL_CURRENT_USER_ID, USER_ID);
+        expectDisplayAssignedToUser(USER_ID, SECONDARY_DISPLAY_ID);
+        expectUserAssignedToDisplay(SECONDARY_DISPLAY_ID, USER_ID);
+        assertUserCanBeAssignedExtraDisplay(USER_ID, OTHER_SECONDARY_DISPLAY_ID);
+
+        listener.verify();
+    }
+
+    @Test
     public final void testStartVisibleBgUser_onSecondaryDisplay_userAlreadyAssigned()
             throws Exception {
         AsyncUserVisibilityListener listener = addListenerForEvents(onVisible(USER_ID));
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
index 32b9864..4249405 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -16,19 +16,13 @@
 
 package com.android.server.accessibility;
 
-import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS;
-import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT;
 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL;
 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_NONE;
 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
-import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE;
-import static android.provider.Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES;
-import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_MENU_IN_SYSTEM;
 
 import static com.android.internal.accessibility.AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME;
 import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
-import static com.android.server.accessibility.AccessibilityManagerService.MENU_SERVICE_RELATIVE_CLASS_NAME;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -100,7 +94,6 @@
 import org.mockito.MockitoAnnotations;
 
 import java.util.ArrayList;
-import java.util.List;
 
 /**
  * APCT tests for {@link AccessibilityManagerService}.
@@ -448,8 +441,7 @@
                 mA11yms.getCurrentUserIdLocked());
         Settings.Secure.putIntForUser(
                 mTestableContext.getContentResolver(),
-                // TODO: replace name with Settings Secure Key
-                "accessibility_magnification_always_on_enabled",
+                Settings.Secure.ACCESSIBILITY_MAGNIFICATION_ALWAYS_ON_ENABLED,
                 1, mA11yms.getCurrentUserIdLocked());
 
         mA11yms.readAlwaysOnMagnificationLocked(userState);
@@ -466,8 +458,7 @@
                 mA11yms.getCurrentUserIdLocked());
         Settings.Secure.putIntForUser(
                 mTestableContext.getContentResolver(),
-                // TODO: replace name with Settings Secure Key
-                "accessibility_magnification_always_on_enabled",
+                Settings.Secure.ACCESSIBILITY_MAGNIFICATION_ALWAYS_ON_ENABLED,
                 1, mA11yms.getCurrentUserIdLocked());
 
         mA11yms.readAlwaysOnMagnificationLocked(userState);
@@ -560,113 +551,6 @@
                 ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME.flattenToString());
     }
 
-    @Test
-    public void testMigrateA11yMenu_ResetSingularComponentToDefaultState() {
-        final ComponentName componentName =
-                ComponentName.createRelative("external", MENU_SERVICE_RELATIVE_CLASS_NAME);
-        when(mMockPackageManager.queryIntentServicesAsUser(any(), any(),
-                eq(mA11yms.getCurrentUserIdLocked()))).thenReturn(
-                List.of(createResolveInfo(componentName)));
-
-        mA11yms.migrateAccessibilityMenuIfNecessaryLocked(mA11yms.getCurrentUserState());
-
-        verify(mMockPackageManager).setComponentEnabledSetting(componentName,
-                PackageManager.COMPONENT_ENABLED_STATE_DEFAULT,
-                PackageManager.DONT_KILL_APP);
-    }
-
-    @Test
-    public void testMigrateA11yMenu_DoNothing_WhenNoMenuComponents() {
-        when(mMockPackageManager.queryIntentServicesAsUser(any(), any(),
-                eq(mA11yms.getCurrentUserIdLocked()))).thenReturn(List.of());
-
-        mA11yms.migrateAccessibilityMenuIfNecessaryLocked(mA11yms.getCurrentUserState());
-
-        verify(mMockPackageManager, never()).setComponentEnabledSetting(any(),
-                anyInt(), anyInt());
-    }
-
-    @Test
-    public void testMigrateA11yMenu_DoNothing_WhenTooManyMenuComponents() {
-        when(mMockPackageManager.queryIntentServicesAsUser(any(), any(),
-                eq(mA11yms.getCurrentUserIdLocked()))).thenReturn(List.of(
-                createResolveInfo(ComponentName.createRelative("external1",
-                        MENU_SERVICE_RELATIVE_CLASS_NAME)),
-                createResolveInfo(ComponentName.createRelative("external2",
-                        MENU_SERVICE_RELATIVE_CLASS_NAME)),
-                createResolveInfo(ComponentName.createRelative("external3",
-                        MENU_SERVICE_RELATIVE_CLASS_NAME))));
-
-        mA11yms.migrateAccessibilityMenuIfNecessaryLocked(mA11yms.getCurrentUserState());
-
-        verify(mMockPackageManager, never()).setComponentEnabledSetting(any(),
-                anyInt(), anyInt());
-    }
-
-    @Test
-    public void testMigrateA11yMenu_DoNothing_WhenNoMenuInSystem() {
-        when(mMockPackageManager.queryIntentServicesAsUser(any(), any(),
-                eq(mA11yms.getCurrentUserIdLocked()))).thenReturn(List.of(
-                createResolveInfo(ComponentName.createRelative("external1",
-                        MENU_SERVICE_RELATIVE_CLASS_NAME)),
-                createResolveInfo(ComponentName.createRelative("external2",
-                        MENU_SERVICE_RELATIVE_CLASS_NAME))));
-
-        mA11yms.migrateAccessibilityMenuIfNecessaryLocked(mA11yms.getCurrentUserState());
-
-        verify(mMockPackageManager, never()).setComponentEnabledSetting(any(),
-                anyInt(), anyInt());
-    }
-
-    @Test
-    public void testMigrateA11yMenu_PerformsMigration() {
-        final ComponentName menuOutsideSystem =
-                ComponentName.createRelative("external", MENU_SERVICE_RELATIVE_CLASS_NAME);
-        final String[] migratedSettings = {
-                ACCESSIBILITY_BUTTON_TARGETS,
-                ACCESSIBILITY_BUTTON_TARGET_COMPONENT,
-                ACCESSIBILITY_SHORTCUT_TARGET_SERVICE,
-                ENABLED_ACCESSIBILITY_SERVICES
-        };
-        // Start the user with Menu-outside-system enabled,
-        for (String setting : migratedSettings) {
-            Settings.Secure.putStringForUser(
-                    mTestableContext.getContentResolver(),
-                    setting,
-                    menuOutsideSystem.flattenToShortString(),
-                    mA11yms.getCurrentUserIdLocked());
-        }
-        // and both Menu versions present.
-        when(mMockPackageManager.queryIntentServicesAsUser(any(), any(),
-                eq(mA11yms.getCurrentUserIdLocked()))).thenReturn(List.of(
-                createResolveInfo(menuOutsideSystem),
-                createResolveInfo(ACCESSIBILITY_MENU_IN_SYSTEM)));
-
-        mA11yms.migrateAccessibilityMenuIfNecessaryLocked(mA11yms.getCurrentUserState());
-
-        // Menu-outside-system should be disabled,
-        verify(mMockPackageManager).setComponentEnabledSetting(menuOutsideSystem,
-                PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
-                PackageManager.DONT_KILL_APP);
-        // and all settings should migrated to Menu-in-system.
-        for (String setting : migratedSettings) {
-            ComponentName componentName = ComponentName.unflattenFromString(
-                    Settings.Secure.getStringForUser(
-                            mTestableContext.getContentResolver(),
-                            setting,
-                            mA11yms.getCurrentUserIdLocked()));
-            assertThat(componentName).isEqualTo(ACCESSIBILITY_MENU_IN_SYSTEM);
-        }
-    }
-
-    private static ResolveInfo createResolveInfo(ComponentName componentName) {
-        ResolveInfo resolveInfo = new ResolveInfo();
-        resolveInfo.serviceInfo = new ServiceInfo();
-        resolveInfo.serviceInfo.packageName = componentName.getPackageName();
-        resolveInfo.serviceInfo.name = componentName.getClassName();
-        return resolveInfo;
-    }
-
     private void mockManageAccessibilityGranted(TestableContext context) {
         context.getTestablePermissions().setPermission(Manifest.permission.MANAGE_ACCESSIBILITY,
                 PackageManager.PERMISSION_GRANTED);
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
index 37ed4ac..93dfee6 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -993,7 +993,8 @@
         }
 
         @Override
-        public AppOpsService getAppOpsService(File file, Handler handler) {
+        public AppOpsService getAppOpsService(File recentAccessesFile, File storageFile,
+                Handler handler) {
             return mAppOpsService;
         }
 
diff --git a/services/tests/servicestests/src/com/android/server/am/AnrHelperTest.java b/services/tests/servicestests/src/com/android/server/am/AnrHelperTest.java
index 162855a..9578993 100644
--- a/services/tests/servicestests/src/com/android/server/am/AnrHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/AnrHelperTest.java
@@ -81,7 +81,8 @@
             final ActivityManagerService service = new ActivityManagerService(
                     new ActivityManagerService.Injector(context) {
                     @Override
-                    public AppOpsService getAppOpsService(File file, Handler handler) {
+                    public AppOpsService getAppOpsService(File recentAccessesFile, File storageFile,
+                            Handler handler) {
                         return null;
                     }
 
diff --git a/services/tests/servicestests/src/com/android/server/am/AppErrorDialogTest.java b/services/tests/servicestests/src/com/android/server/am/AppErrorDialogTest.java
index 24f8eab..19bc1c2 100644
--- a/services/tests/servicestests/src/com/android/server/am/AppErrorDialogTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/AppErrorDialogTest.java
@@ -73,7 +73,8 @@
         mContext = getInstrumentation().getTargetContext();
         mService = new ActivityManagerService(new ActivityManagerService.Injector(mContext) {
             @Override
-            public AppOpsService getAppOpsService(File file, Handler handler) {
+            public AppOpsService getAppOpsService(File recentAccessesFile, File storageFile,
+                    Handler handler) {
                 return null;
             }
 
diff --git a/services/tests/servicestests/src/com/android/server/am/CoreSettingsObserverTest.java b/services/tests/servicestests/src/com/android/server/am/CoreSettingsObserverTest.java
index 5f55f09..fd0b679 100644
--- a/services/tests/servicestests/src/com/android/server/am/CoreSettingsObserverTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/CoreSettingsObserverTest.java
@@ -179,7 +179,8 @@
         }
 
         @Override
-        public AppOpsService getAppOpsService(File file, Handler handler) {
+        public AppOpsService getAppOpsService(File recentAccessesFile, File storageFile,
+                Handler handler) {
             return null;
         }
 
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
index 32cb8c4..ad6d6b9 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
@@ -1316,10 +1316,10 @@
 
         mRecoverableKeyStoreManager.startRemoteLockscreenValidation(mLockSettingsService);
 
-        // TODO(b/254335492): Check new system permission
         verify(mMockContext, times(1))
                 .enforceCallingOrSelfPermission(
-                        eq(Manifest.permission.RECOVER_KEYSTORE), any());
+                        eq(Manifest.permission.CHECK_REMOTE_LOCKSCREEN), any());
+        mRemoteLockscreenValidationSessionStorage.finishSession(mUserId);
     }
     @Test
     public void startRemoteLockscreenValidation_returnsCredentailsType() throws Exception {
diff --git a/services/tests/servicestests/src/com/android/server/os/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/os/TEST_MAPPING
index 9902446..5a46f8c4 100644
--- a/services/tests/servicestests/src/com/android/server/os/TEST_MAPPING
+++ b/services/tests/servicestests/src/com/android/server/os/TEST_MAPPING
@@ -1,7 +1,7 @@
 {
   "presubmit": [
     {
-      "name": "BugreportManagerServiceImplTests",
+      "name": "FrameworksServicesTests",
       "options": [
         {
           "include-filter": "com.android.server.os."
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceShellCommandTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceShellCommandTest.java
new file mode 100644
index 0000000..4434a32
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceShellCommandTest.java
@@ -0,0 +1,124 @@
+/*
+ * 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.pm;
+
+import static junit.framework.Assert.assertEquals;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import static java.io.FileDescriptor.err;
+import static java.io.FileDescriptor.in;
+import static java.io.FileDescriptor.out;
+
+import android.app.PropertyInvalidatedCache;
+import android.content.Context;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
+import android.os.UserHandle;
+import android.platform.test.annotations.Presubmit;
+import android.util.ArrayMap;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.server.LocalServices;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.io.PrintWriter;
+
+/**
+ * Test class for {@link UserManagerServiceShellCommand}.
+ *
+ * runtest atest UserManagerServiceShellCommandTest
+ */
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class UserManagerServiceShellCommandTest {
+
+    private UserManagerServiceShellCommand mCommand;
+    private UserManagerService mUserManagerService;
+    private @Mock LockPatternUtils mLockPatternUtils;
+    private final Binder mBinder = new Binder();
+    private final ShellCallback mShellCallback = new ShellCallback();
+    private final ResultReceiver mResultReceiver = new ResultReceiver(
+            new Handler(Looper.getMainLooper()));
+    private ByteArrayOutputStream mOutStream;
+    private PrintWriter mWriter;
+
+    @Before
+    public void setUp() throws Exception {
+        mOutStream = new ByteArrayOutputStream();
+        mWriter = new PrintWriter(new PrintStream(mOutStream));
+        MockitoAnnotations.initMocks(this);
+
+        if (Looper.myLooper() == null) {
+            Looper.prepare();
+        }
+        // Disable binder caches in this process.
+        PropertyInvalidatedCache.disableForTestMode();
+
+        LocalServices.removeServiceForTest(UserManagerInternal.class);
+        final Context context = InstrumentationRegistry.getTargetContext();
+
+        UserManagerService serviceInstance = new UserManagerService(context);
+        mUserManagerService = spy(serviceInstance);
+
+        ArrayMap<String, UserTypeDetails> userTypes = UserTypeFactory.getUserTypes();
+        UserSystemPackageInstaller userSystemPackageInstaller =
+                new UserSystemPackageInstaller(mUserManagerService, userTypes);
+        UserManagerServiceShellCommand cmd = new UserManagerServiceShellCommand(mUserManagerService,
+                userSystemPackageInstaller, mLockPatternUtils, context);
+        mCommand = spy(cmd);
+    }
+
+    @Test
+    public void testMainUser() {
+        when(mUserManagerService.getMainUserId()).thenReturn(12);
+        doReturn(mWriter).when(mCommand).getOutPrintWriter();
+
+        assertEquals(0, mCommand.exec(mBinder, in, out, err,
+                new String[]{"get-main-user"}, mShellCallback, mResultReceiver));
+
+        mWriter.flush();
+        assertEquals("Main user id: 12", mOutStream.toString().trim());
+    }
+
+    @Test
+    public void testMainUserNull() {
+        when(mUserManagerService.getMainUserId()).thenReturn(UserHandle.USER_NULL);
+        doReturn(mWriter).when(mCommand).getOutPrintWriter();
+
+        assertEquals(1, mCommand.exec(mBinder, in, out, err,
+                new String[]{"get-main-user"}, mShellCallback, mResultReceiver));
+        mWriter.flush();
+        assertEquals("Couldn't get main user.", mOutStream.toString().trim());
+    }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java
index 00aa520..4af0323 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -16,7 +16,6 @@
 
 package com.android.server.pm;
 
-import static android.os.UserManager.DISALLOW_BLUETOOTH;
 import static android.os.UserManager.DISALLOW_USER_SWITCH;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -41,7 +40,6 @@
 import com.android.server.LocalServices;
 
 import org.junit.After;
-import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -188,13 +186,11 @@
         while (mUserManagerService.userExists(incorrectId)) {
             incorrectId++;
         }
-        try {
-            mUserManagerService.setUserRestriction(DISALLOW_BLUETOOTH, true, incorrectId);
-            Assert.fail();
-        } catch (IllegalArgumentException e) {
-            //Exception is expected to be thrown if user ID does not exist.
-            // IllegalArgumentException thrown means this test is successful.
-        }
+        assertThat(mUserManagerService.hasUserRestriction(DISALLOW_USER_SWITCH, incorrectId))
+                .isFalse();
+        mUserManagerService.setUserRestriction(DISALLOW_USER_SWITCH, true, incorrectId);
+        assertThat(mUserManagerService.hasUserRestriction(DISALLOW_USER_SWITCH, incorrectId))
+                .isFalse();
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java
index 92fddc7..d999aa3 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java
@@ -22,6 +22,7 @@
 import static android.content.pm.UserInfo.FLAG_FULL;
 import static android.content.pm.UserInfo.FLAG_GUEST;
 import static android.content.pm.UserInfo.FLAG_INITIALIZED;
+import static android.content.pm.UserInfo.FLAG_MAIN;
 import static android.content.pm.UserInfo.FLAG_MANAGED_PROFILE;
 import static android.content.pm.UserInfo.FLAG_PROFILE;
 import static android.content.pm.UserInfo.FLAG_RESTRICTED;
@@ -206,6 +207,13 @@
         assertFalse("Switching to a profiles should be disabled", userInfo.supportsSwitchTo());
     }
 
+    /** Test UserInfo.canHaveProfile for main user */
+    @Test
+    public void testCanHaveProfile() throws Exception {
+        UserInfo userInfo = createUser(100, FLAG_MAIN, null);
+        assertTrue("Main users can have profile", userInfo.canHaveProfile());
+    }
+
     /** Tests upgradeIfNecessaryLP (but without locking) for upgrading from version 8 to 9+. */
     @Test
     public void testUpgradeIfNecessaryLP_9() {
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index b4dd631..ddd879a 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -26,7 +26,6 @@
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.content.Context;
-import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
 import android.content.pm.UserProperties;
@@ -46,8 +45,6 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.compatibility.common.util.BlockingBroadcastReceiver;
-
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Range;
 
@@ -61,10 +58,13 @@
 import java.util.concurrent.Executors;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
-import java.util.function.Function;
 import java.util.stream.Collectors;
 
-/** Test {@link UserManager} functionality. */
+/**
+ * Test {@link UserManager} functionality.
+ *
+ *  atest com.android.server.pm.UserManagerTest
+ */
 @Postsubmit
 @RunWith(AndroidJUnit4.class)
 public final class UserManagerTest {
@@ -88,13 +88,17 @@
     private PackageManager mPackageManager;
     private ArraySet<Integer> mUsersToRemove;
     private UserSwitchWaiter mUserSwitchWaiter;
+    private UserRemovalWaiter mUserRemovalWaiter;
+    private int mOriginalCurrentUserId;
 
     @Before
     public void setUp() throws Exception {
+        mOriginalCurrentUserId = ActivityManager.getCurrentUser();
         mUserManager = UserManager.get(mContext);
         mActivityManager = mContext.getSystemService(ActivityManager.class);
         mPackageManager = mContext.getPackageManager();
         mUserSwitchWaiter = new UserSwitchWaiter(TAG, SWITCH_USER_TIMEOUT_SECONDS);
+        mUserRemovalWaiter = new UserRemovalWaiter(mContext, TAG, REMOVE_USER_TIMEOUT_SECONDS);
 
         mUsersToRemove = new ArraySet<>();
         removeExistingUsers();
@@ -102,10 +106,14 @@
 
     @After
     public void tearDown() throws Exception {
+        if (mOriginalCurrentUserId != ActivityManager.getCurrentUser()) {
+            switchUser(mOriginalCurrentUserId);
+        }
         mUserSwitchWaiter.close();
 
         // Making a copy of mUsersToRemove to avoid ConcurrentModificationException
         mUsersToRemove.stream().toList().forEach(this::removeUser);
+        mUserRemovalWaiter.close();
     }
 
     private void removeExistingUsers() {
@@ -394,11 +402,10 @@
         mUserManager.setUserRestriction(UserManager.DISALLOW_REMOVE_USER, /* value= */ true,
                 asHandle(currentUser));
         try {
-            runThenWaitForUserRemoval(() -> {
-                assertThat(mUserManager.removeUserWhenPossible(user1.getUserHandle(),
-                        /* overrideDevicePolicy= */ true))
-                        .isEqualTo(UserManager.REMOVE_RESULT_REMOVED);
-            }, user1.id); // wait for user removal
+            assertThat(mUserManager.removeUserWhenPossible(user1.getUserHandle(),
+                    /* overrideDevicePolicy= */ true))
+                    .isEqualTo(UserManager.REMOVE_RESULT_REMOVED);
+            waitForUserRemoval(user1.id);
         } finally {
             mUserManager.setUserRestriction(UserManager.DISALLOW_REMOVE_USER, /* value= */ false,
                     asHandle(currentUser));
@@ -463,11 +470,10 @@
         assertThat(hasUser(user1.id)).isTrue();
         assertThat(getUser(user1.id).isEphemeral()).isTrue();
 
-        runThenWaitForUserRemoval(() -> {
-            // Switch back to the starting user.
-            switchUser(startUser);
-            // User will be removed once switch is complete
-        }, user1.id); // wait for user removal
+        // Switch back to the starting user.
+        switchUser(startUser);
+        // User will be removed once switch is complete
+        waitForUserRemoval(user1.id);
 
         assertThat(hasUser(user1.id)).isFalse();
     }
@@ -480,18 +486,17 @@
         // Switch to the user just created.
         switchUser(testUser.id);
 
-        runThenWaitForUserRemoval(() -> {
-            switchUserThenRun(startUser, () -> {
-                // While the switch is happening, call removeUserWhenPossible for the current user.
-                assertThat(mUserManager.removeUserWhenPossible(testUser.getUserHandle(),
-                        /* overrideDevicePolicy= */ false))
-                        .isEqualTo(UserManager.REMOVE_RESULT_DEFERRED);
+        switchUserThenRun(startUser, () -> {
+            // While the switch is happening, call removeUserWhenPossible for the current user.
+            assertThat(mUserManager.removeUserWhenPossible(testUser.getUserHandle(),
+                    /* overrideDevicePolicy= */ false))
+                    .isEqualTo(UserManager.REMOVE_RESULT_DEFERRED);
 
-                assertThat(hasUser(testUser.id)).isTrue();
-                assertThat(getUser(testUser.id).isEphemeral()).isTrue();
-            }); // wait for user switch - startUser
-            // User will be removed once switch is complete
-        }, testUser.id); // wait for user removal
+            assertThat(hasUser(testUser.id)).isTrue();
+            assertThat(getUser(testUser.id).isEphemeral()).isTrue();
+        }); // wait for user switch - startUser
+        // User will be removed once switch is complete
+        waitForUserRemoval(testUser.id);
 
         assertThat(hasUser(testUser.id)).isFalse();
     }
@@ -512,11 +517,10 @@
             assertThat(getUser(testUser.id).isEphemeral()).isTrue();
         }); // wait for user switch - testUser
 
-        runThenWaitForUserRemoval(() -> {
-            // Switch back to the starting user.
-            switchUser(startUser);
-            // User will be removed once switch is complete
-        }, testUser.id); // wait for user removal
+        // Switch back to the starting user.
+        switchUser(startUser);
+        // User will be removed once switch is complete
+        waitForUserRemoval(testUser.id);
 
         assertThat(hasUser(testUser.id)).isFalse();
     }
@@ -526,11 +530,10 @@
     public void testRemoveUserWhenPossible_nonCurrentUserRemoved() throws Exception {
         final UserInfo user1 = createUser("User 1", /* flags= */ 0);
 
-        runThenWaitForUserRemoval(() -> {
-            assertThat(mUserManager.removeUserWhenPossible(user1.getUserHandle(),
-                    /* overrideDevicePolicy= */ false))
-                    .isEqualTo(UserManager.REMOVE_RESULT_REMOVED);
-        }, user1.id); // wait for user removal
+        assertThat(mUserManager.removeUserWhenPossible(user1.getUserHandle(),
+                /* overrideDevicePolicy= */ false))
+                .isEqualTo(UserManager.REMOVE_RESULT_REMOVED);
+        waitForUserRemoval(user1.id);
 
         assertThat(hasUser(user1.id)).isFalse();
     }
@@ -549,11 +552,10 @@
                 UserManager.USER_TYPE_PROFILE_MANAGED,
                 parentUser.id);
 
-        runThenWaitForUserRemoval(() -> {
-            assertThat(mUserManager.removeUserWhenPossible(parentUser.getUserHandle(),
-                    /* overrideDevicePolicy= */ false))
-                    .isEqualTo(UserManager.REMOVE_RESULT_REMOVED);
-        }, parentUser.id); // wait for user removal
+        assertThat(mUserManager.removeUserWhenPossible(parentUser.getUserHandle(),
+                /* overrideDevicePolicy= */ false))
+                .isEqualTo(UserManager.REMOVE_RESULT_REMOVED);
+        waitForUserRemoval(parentUser.id);
 
         assertThat(hasUser(parentUser.id)).isFalse();
         assertThat(hasUser(cloneProfileUser.id)).isFalse();
@@ -1120,7 +1122,7 @@
 
     @Nullable
     private UserInfo getUser(int id) {
-        List<UserInfo> list = mUserManager.getUsers();
+        List<UserInfo> list = mUserManager.getAliveUsers();
 
         for (UserInfo user : list) {
             if (user.id == id) {
@@ -1275,7 +1277,7 @@
     @MediumTest
     @Test
     public void testConcurrentUserCreate() throws Exception {
-        int userCount = mUserManager.getUserCount();
+        int userCount = mUserManager.getAliveUsers().size();
         int maxSupportedUsers = UserManager.getMaxSupportedUsers();
         int canBeCreatedCount = maxSupportedUsers - userCount;
         // Test exceeding the limit while running in parallel
@@ -1294,7 +1296,7 @@
         }
         es.shutdown();
         es.awaitTermination(20, TimeUnit.SECONDS);
-        assertThat(mUserManager.getUserCount()).isEqualTo(maxSupportedUsers);
+        assertThat(mUserManager.getAliveUsers().size()).isEqualTo(maxSupportedUsers);
         assertThat(created.get()).isEqualTo(canBeCreatedCount);
     }
 
@@ -1451,41 +1453,18 @@
     }
 
     private void removeUser(UserHandle userHandle) {
-        runThenWaitForUserRemoval(
-                () -> mUserManager.removeUser(userHandle),
-                userHandle == null ? UserHandle.USER_NULL : userHandle.getIdentifier()
-        );
+        mUserManager.removeUser(userHandle);
+        waitForUserRemoval(userHandle.getIdentifier());
     }
 
     private void removeUser(int userId) {
-        runThenWaitForUserRemoval(
-                () -> mUserManager.removeUser(userId),
-                userId
-        );
+        mUserManager.removeUser(userId);
+        waitForUserRemoval(userId);
     }
 
-    private void runThenWaitForUserRemoval(Runnable runnable, int userIdToWaitUntilDeleted) {
-        if (!hasUser(userIdToWaitUntilDeleted)) {
-            runnable.run();
-            mUsersToRemove.remove(userIdToWaitUntilDeleted);
-            return;
-        }
-
-        Function<Intent, Boolean> checker = intent -> {
-            UserHandle userHandle = intent.getParcelableExtra(Intent.EXTRA_USER, UserHandle.class);
-            return userHandle != null && userHandle.getIdentifier() == userIdToWaitUntilDeleted;
-        };
-
-        BlockingBroadcastReceiver blockingBroadcastReceiver = BlockingBroadcastReceiver.create(
-                mContext, Intent.ACTION_USER_REMOVED, checker);
-
-        blockingBroadcastReceiver.setTimeout(REMOVE_USER_TIMEOUT_SECONDS);
-        blockingBroadcastReceiver.register();
-
-        try (blockingBroadcastReceiver) {
-            runnable.run();
-        }
-        mUsersToRemove.remove(userIdToWaitUntilDeleted);
+    private void waitForUserRemoval(int userId) {
+        mUserRemovalWaiter.waitFor(userId);
+        mUsersToRemove.remove(userId);
     }
 
     private UserInfo createUser(String name, int flags) {
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserRemovalWaiter.java b/services/tests/servicestests/src/com/android/server/pm/UserRemovalWaiter.java
new file mode 100644
index 0000000..9e1af0c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/UserRemovalWaiter.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import static org.junit.Assert.fail;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.Log;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class UserRemovalWaiter extends BroadcastReceiver implements Closeable {
+
+    private final Context mContext;
+    private final UserManager mUserManager;
+    private final String mTag;
+    private final long mTimeoutMillis;
+    private final Map<Integer, CountDownLatch> mMap = new ConcurrentHashMap<>();
+
+    private CountDownLatch getLatch(final int userId) {
+        return mMap.computeIfAbsent(userId, absentKey -> new CountDownLatch(1));
+    }
+
+    public UserRemovalWaiter(Context context, String tag, int timeoutInSeconds) {
+        mContext = context;
+        mUserManager = UserManager.get(mContext);
+        mTag = tag;
+        mTimeoutMillis = timeoutInSeconds * 1000L;
+
+        mContext.registerReceiver(this, new IntentFilter(Intent.ACTION_USER_REMOVED));
+    }
+
+    @Override
+    public void close() throws IOException {
+        mContext.unregisterReceiver(this);
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        if (Intent.ACTION_USER_REMOVED.equals(intent.getAction())) {
+            int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
+            Log.i(mTag, "ACTION_USER_REMOVED received for user " + userId);
+            getLatch(userId).countDown();
+        }
+    }
+
+    /**
+     * Waits for the removal of the given user, or fails if it times out.
+     */
+    public void waitFor(int userId) {
+        Log.i(mTag, "Waiting for user " + userId + " to be removed");
+        CountDownLatch latch = getLatch(userId);
+        long startTime = System.currentTimeMillis();
+        while (System.currentTimeMillis() - startTime < mTimeoutMillis) {
+            if (hasUserGone(userId) || waitLatchForOneSecond(latch)) {
+                Log.i(mTag, "User " + userId + " is removed in "
+                        + (System.currentTimeMillis() - startTime) + " ms");
+                return;
+            }
+        }
+        fail("Timeout waiting for user removal. userId = " + userId);
+    }
+
+    private boolean hasUserGone(int userId) {
+        return mUserManager.getAliveUsers().stream().noneMatch(x -> x.id == userId);
+    }
+
+    private boolean waitLatchForOneSecond(CountDownLatch latch) {
+        try {
+            return latch.await(1, TimeUnit.SECONDS);
+        } catch (InterruptedException e) {
+            Log.e(mTag, "Thread interrupted unexpectedly.", e);
+            return false;
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/InputDeviceDelegateTest.java b/services/tests/servicestests/src/com/android/server/vibrator/InputDeviceDelegateTest.java
index 2ac8b37..6edef75 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/InputDeviceDelegateTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/InputDeviceDelegateTest.java
@@ -263,7 +263,8 @@
     public void vibrateIfAvailable_withNoInputDevice_returnsFalse() {
         assertFalse(mInputDeviceDelegate.isAvailable());
         assertFalse(mInputDeviceDelegate.vibrateIfAvailable(
-                UID, PACKAGE_NAME, SYNCED_EFFECT, REASON, VIBRATION_ATTRIBUTES));
+                new Vibration.CallerInfo(VIBRATION_ATTRIBUTES, UID, -1, PACKAGE_NAME, REASON),
+                SYNCED_EFFECT));
     }
 
     @Test
@@ -277,7 +278,8 @@
         mInputDeviceDelegate.updateInputDeviceVibrators(/* vibrateInputDevices= */ true);
 
         assertTrue(mInputDeviceDelegate.vibrateIfAvailable(
-                UID, PACKAGE_NAME, SYNCED_EFFECT, REASON, VIBRATION_ATTRIBUTES));
+                new Vibration.CallerInfo(VIBRATION_ATTRIBUTES, UID, -1, PACKAGE_NAME, REASON),
+                SYNCED_EFFECT));
         verify(mIInputManagerMock).vibrateCombined(eq(1), same(SYNCED_EFFECT), any());
         verify(mIInputManagerMock).vibrateCombined(eq(2), same(SYNCED_EFFECT), any());
     }
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java
index 508e7b0f..d50aca9 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java
@@ -564,17 +564,17 @@
 
         for (int usage : ALL_USAGES) {
             // Non-system vibration
-            assertFalse(mVibrationSettings.shouldCancelVibrationOnScreenOff(
-                    UID, "some.app", usage, vibrateStartTime));
+            assertFalse(mVibrationSettings.shouldCancelVibrationOnScreenOff(createCallerInfo(
+                    UID, "some.app", usage), vibrateStartTime));
             // Vibration with UID zero
             assertFalse(mVibrationSettings.shouldCancelVibrationOnScreenOff(
-                    /* uid= */ 0, "", usage, vibrateStartTime));
+                    createCallerInfo(/* uid= */ 0, "", usage), vibrateStartTime));
             // System vibration
             assertFalse(mVibrationSettings.shouldCancelVibrationOnScreenOff(
-                    Process.SYSTEM_UID, "", usage, vibrateStartTime));
+                    createCallerInfo(Process.SYSTEM_UID, "", usage), vibrateStartTime));
             // SysUI vibration
             assertFalse(mVibrationSettings.shouldCancelVibrationOnScreenOff(
-                    UID, SYSUI_PACKAGE_NAME, usage, vibrateStartTime));
+                    createCallerInfo(UID, SYSUI_PACKAGE_NAME, usage), vibrateStartTime));
         }
     }
 
@@ -592,16 +592,16 @@
             for (int usage : ALL_USAGES) {
                 // Non-system vibration
                 assertFalse(mVibrationSettings.shouldCancelVibrationOnScreenOff(
-                        UID, "some.app", usage, vibrateStartTime));
+                        createCallerInfo(UID, "some.app", usage), vibrateStartTime));
                 // Vibration with UID zero
                 assertFalse(mVibrationSettings.shouldCancelVibrationOnScreenOff(
-                        /* uid= */ 0, "", usage, vibrateStartTime));
+                        createCallerInfo(/* uid= */ 0, "", usage), vibrateStartTime));
                 // System vibration
                 assertFalse(mVibrationSettings.shouldCancelVibrationOnScreenOff(
-                        Process.SYSTEM_UID, "", usage, vibrateStartTime));
+                        createCallerInfo(Process.SYSTEM_UID, "", usage), vibrateStartTime));
                 // SysUI vibration
                 assertFalse(mVibrationSettings.shouldCancelVibrationOnScreenOff(
-                        UID, SYSUI_PACKAGE_NAME, usage, vibrateStartTime));
+                        createCallerInfo(UID, SYSUI_PACKAGE_NAME, usage), vibrateStartTime));
             }
         }
     }
@@ -613,7 +613,7 @@
 
         for (int usage : ALL_USAGES) {
             assertTrue(mVibrationSettings.shouldCancelVibrationOnScreenOff(
-                    UID, "some.app", usage, vibrateStartTime));
+                    createCallerInfo(UID, "some.app", usage), vibrateStartTime));
         }
     }
 
@@ -626,10 +626,10 @@
             if (usage == USAGE_TOUCH || usage == USAGE_HARDWARE_FEEDBACK
                     || usage == USAGE_PHYSICAL_EMULATION) {
                 assertFalse(mVibrationSettings.shouldCancelVibrationOnScreenOff(
-                        /* uid= */ 0, "", usage, vibrateStartTime));
+                        createCallerInfo(/* uid= */ 0, "", usage), vibrateStartTime));
             } else {
                 assertTrue(mVibrationSettings.shouldCancelVibrationOnScreenOff(
-                        /* uid= */ 0, "", usage, vibrateStartTime));
+                        createCallerInfo(/* uid= */ 0, "", usage), vibrateStartTime));
             }
         }
     }
@@ -643,10 +643,10 @@
             if (usage == USAGE_TOUCH || usage == USAGE_HARDWARE_FEEDBACK
                     || usage == USAGE_PHYSICAL_EMULATION) {
                 assertFalse(mVibrationSettings.shouldCancelVibrationOnScreenOff(
-                        Process.SYSTEM_UID, "", usage, vibrateStartTime));
+                        createCallerInfo(Process.SYSTEM_UID, "", usage), vibrateStartTime));
             } else {
                 assertTrue(mVibrationSettings.shouldCancelVibrationOnScreenOff(
-                        Process.SYSTEM_UID, "", usage, vibrateStartTime));
+                        createCallerInfo(Process.SYSTEM_UID, "", usage), vibrateStartTime));
             }
         }
     }
@@ -660,10 +660,10 @@
             if (usage == USAGE_TOUCH || usage == USAGE_HARDWARE_FEEDBACK
                     || usage == USAGE_PHYSICAL_EMULATION) {
                 assertFalse(mVibrationSettings.shouldCancelVibrationOnScreenOff(
-                        UID, SYSUI_PACKAGE_NAME, usage, vibrateStartTime));
+                        createCallerInfo(UID, SYSUI_PACKAGE_NAME, usage), vibrateStartTime));
             } else {
                 assertTrue(mVibrationSettings.shouldCancelVibrationOnScreenOff(
-                        UID, SYSUI_PACKAGE_NAME, usage, vibrateStartTime));
+                        createCallerInfo(UID, SYSUI_PACKAGE_NAME, usage), vibrateStartTime));
             }
         }
     }
@@ -755,10 +755,10 @@
 
     private void assertVibrationIgnoredForUsageAndDisplay(@VibrationAttributes.Usage int usage,
             int displayId, Vibration.Status expectedStatus) {
-        assertEquals(errorMessageForUsage(usage),
-                expectedStatus,
-                mVibrationSettings.shouldIgnoreVibration(UID, displayId,
-                        VibrationAttributes.createForUsage(usage)));
+        Vibration.CallerInfo callerInfo = new Vibration.CallerInfo(
+                VibrationAttributes.createForUsage(usage), UID, displayId, null, null);
+        assertEquals(errorMessageForUsage(usage), expectedStatus,
+                mVibrationSettings.shouldIgnoreVibration(callerInfo));
     }
 
     private void assertVibrationNotIgnoredForUsage(@VibrationAttributes.Usage int usage) {
@@ -767,24 +767,22 @@
 
     private void assertVibrationNotIgnoredForUsageAndFlags(@VibrationAttributes.Usage int usage,
             @VibrationAttributes.Flag int flags) {
-        assertVibrationNotIgnoredForUsageAndFlagsAndDidsplay(usage, Display.DEFAULT_DISPLAY, flags);
+        assertVibrationNotIgnoredForUsageAndFlagsAndDisplay(usage, Display.DEFAULT_DISPLAY, flags);
     }
 
     private void assertVibrationNotIgnoredForUsageAndDisplay(@VibrationAttributes.Usage int usage,
             int displayId) {
-        assertVibrationNotIgnoredForUsageAndFlagsAndDidsplay(usage, displayId, /* flags= */ 0);
+        assertVibrationNotIgnoredForUsageAndFlagsAndDisplay(usage, displayId, /* flags= */ 0);
     }
 
-    private void assertVibrationNotIgnoredForUsageAndFlagsAndDidsplay(
+    private void assertVibrationNotIgnoredForUsageAndFlagsAndDisplay(
             @VibrationAttributes.Usage int usage, int displayId,
             @VibrationAttributes.Flag int flags) {
+        Vibration.CallerInfo callerInfo = new Vibration.CallerInfo(
+                new VibrationAttributes.Builder().setUsage(usage).setFlags(flags).build(), UID,
+                displayId, null, null);
         assertNull(errorMessageForUsage(usage),
-                mVibrationSettings.shouldIgnoreVibration(UID,
-                        displayId,
-                        new VibrationAttributes.Builder()
-                                .setUsage(usage)
-                                .setFlags(flags)
-                                .build()));
+                mVibrationSettings.shouldIgnoreVibration(callerInfo));
     }
 
 
@@ -826,4 +824,10 @@
         when(mPowerManagerInternalMock.getLastGoToSleep()).thenReturn(
                 new PowerManager.SleepData(sleepTime, reason));
     }
+
+    private Vibration.CallerInfo createCallerInfo(int uid, String opPkg,
+            @VibrationAttributes.Usage int usage) {
+        VibrationAttributes attrs = VibrationAttributes.createForUsage(usage);
+        return new Vibration.CallerInfo(attrs, uid, VIRTUAL_DISPLAY_ID, opPkg, null);
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
index 9facb4b..12810bb 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -154,10 +154,10 @@
     @Test
     public void vibrate_noVibrator_ignoresVibration() {
         mVibratorProviders.clear();
-        long vibrationId = 1;
         CombinedVibration effect = CombinedVibration.createParallel(
                 VibrationEffect.get(VibrationEffect.EFFECT_CLICK));
-        startThreadAndDispatcher(vibrationId, effect);
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
         waitForCompletion();
 
         verify(mControllerCallbacks, never()).onComplete(anyInt(), eq(vibrationId));
@@ -166,12 +166,12 @@
 
     @Test
     public void vibrate_missingVibrators_ignoresVibration() {
-        long vibrationId = 1;
         CombinedVibration effect = CombinedVibration.startSequential()
                 .addNext(2, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
                 .addNext(3, VibrationEffect.get(VibrationEffect.EFFECT_TICK))
                 .combine();
-        startThreadAndDispatcher(vibrationId, effect);
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
         waitForCompletion();
 
         verify(mControllerCallbacks, never()).onComplete(anyInt(), eq(vibrationId));
@@ -182,9 +182,9 @@
     public void vibrate_singleVibratorOneShot_runsVibrationAndSetsAmplitude() throws Exception {
         mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
 
-        long vibrationId = 1;
         VibrationEffect effect = VibrationEffect.createOneShot(10, 100);
-        startThreadAndDispatcher(vibrationId, effect);
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
         waitForCompletion();
 
         verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L));
@@ -201,9 +201,9 @@
     @Test
     public void vibrate_oneShotWithoutAmplitudeControl_runsVibrationWithDefaultAmplitude()
             throws Exception {
-        long vibrationId = 1;
         VibrationEffect effect = VibrationEffect.createOneShot(10, 100);
-        startThreadAndDispatcher(vibrationId, effect);
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
         waitForCompletion();
 
         verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L));
@@ -222,10 +222,10 @@
             throws Exception {
         mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
 
-        long vibrationId = 1;
         VibrationEffect effect = VibrationEffect.createWaveform(
                 new long[]{5, 5, 5}, new int[]{1, 2, 3}, -1);
-        startThreadAndDispatcher(vibrationId, effect);
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
         waitForCompletion();
 
         verify(mManagerHooks).noteVibratorOn(eq(UID), eq(15L));
@@ -246,10 +246,10 @@
         FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
         fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
 
-        long vibrationId = 1;
         int[] amplitudes = new int[]{1, 2, 3};
         VibrationEffect effect = VibrationEffect.createWaveform(new long[]{5, 5, 5}, amplitudes, 0);
-        VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect);
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
 
         assertTrue(
                 waitUntil(() -> fakeVibrator.getAmplitudes().size() > 2 * amplitudes.length,
@@ -259,8 +259,9 @@
         assertTrue(mControllers.get(VIBRATOR_ID).isVibrating());
 
         Vibration.EndInfo cancelVibrationInfo = new Vibration.EndInfo(
-                Vibration.Status.CANCELLED_SUPERSEDED, /* endedByUid= */ 1,
-                /* endedByUsage= */ VibrationAttributes.USAGE_ALARM);
+                Vibration.Status.CANCELLED_SUPERSEDED, new Vibration.CallerInfo(
+                VibrationAttributes.createForUsage(VibrationAttributes.USAGE_ALARM), /* uid= */
+                1, /* displayId= */ -1, /* opPkg= */ null, /* reason= */ null));
         conductor.notifyCancelled(
                 cancelVibrationInfo,
                 /* immediate= */ false);
@@ -287,11 +288,11 @@
         FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
         fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
 
-        long vibrationId = 1;
         int[] amplitudes = new int[]{1, 2, 3};
         VibrationEffect effect = VibrationEffect.createWaveform(
                 new long[]{1, 10, 100}, amplitudes, 0);
-        VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect);
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
 
         assertTrue(waitUntil(() -> !fakeVibrator.getAmplitudes().isEmpty(), TEST_TIMEOUT_MILLIS));
         conductor.notifyCancelled(
@@ -315,7 +316,6 @@
         fakeVibrator.setMaxAmplitudes(1, 1, 1);
         fakeVibrator.setPwleSizeMax(10);
 
-        long vibrationId = 1;
         VibrationEffect effect = VibrationEffect.startWaveform(targetAmplitude(1))
                 // Very long segment so thread will be cancelled after first PWLE is triggered.
                 .addTransition(Duration.ofMillis(100), targetFrequency(100))
@@ -323,7 +323,8 @@
         VibrationEffect repeatingEffect = VibrationEffect.startComposition()
                 .repeatEffectIndefinitely(effect)
                 .compose();
-        VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, repeatingEffect);
+        VibrationStepConductor conductor = startThreadAndDispatcher(repeatingEffect);
+        long vibrationId = conductor.getVibration().id;
 
         assertTrue(waitUntil(() -> !fakeVibrator.getEffectSegments(vibrationId).isEmpty(),
                 TEST_TIMEOUT_MILLIS));
@@ -346,7 +347,6 @@
         fakeVibrator.setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK);
         fakeVibrator.setCompositionSizeMax(10);
 
-        long vibrationId = 1;
         VibrationEffect effect = VibrationEffect.startComposition()
                 // Very long delay so thread will be cancelled after first PWLE is triggered.
                 .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100)
@@ -354,7 +354,8 @@
         VibrationEffect repeatingEffect = VibrationEffect.startComposition()
                 .repeatEffectIndefinitely(effect)
                 .compose();
-        VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, repeatingEffect);
+        VibrationStepConductor conductor = startThreadAndDispatcher(repeatingEffect);
+        long vibrationId = conductor.getVibration().id;
 
         assertTrue(waitUntil(() -> !fakeVibrator.getEffectSegments(vibrationId).isEmpty(),
                 TEST_TIMEOUT_MILLIS));
@@ -375,11 +376,11 @@
         FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
         fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
 
-        long vibrationId = 1;
         int[] amplitudes = new int[]{1, 2, 3};
         VibrationEffect effect = VibrationEffect.createWaveform(
                 new long[]{5000, 500, 50}, amplitudes, 0);
-        VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect);
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
 
         assertTrue(waitUntil(() -> !fakeVibrator.getAmplitudes().isEmpty(), TEST_TIMEOUT_MILLIS));
         conductor.notifyCancelled(
@@ -400,11 +401,11 @@
         FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
         fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
 
-        long vibrationId = 1;
         int[] amplitudes = new int[]{1, 2};
         VibrationEffect effect = VibrationEffect.createWaveform(
                 new long[]{4900, 50}, amplitudes, 0);
-        VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect);
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
 
         assertTrue(waitUntil(() -> fakeVibrator.getEffectSegments(vibrationId).size() > 1,
                 5000 + TEST_TIMEOUT_MILLIS));
@@ -433,13 +434,13 @@
         mVibratorProviders.get(VIBRATOR_ID).setSupportedPrimitives(
                 VibrationEffect.Composition.PRIMITIVE_CLICK);
 
-        long vibrationId = 1;
         VibrationEffect effect = VibrationEffect.startComposition()
                 .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100)
                 .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100)
                 .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100)
                 .compose();
-        VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect);
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
 
         assertTrue(waitUntil(() -> mControllers.get(VIBRATOR_ID).isVibrating(),
                 TEST_TIMEOUT_MILLIS));
@@ -466,9 +467,9 @@
             throws Exception {
         mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
 
-        long vibrationId = 1;
         VibrationEffect effect = VibrationEffect.createWaveform(new long[]{100}, new int[]{100}, 0);
-        VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect);
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
 
         assertTrue(waitUntil(() -> mControllers.get(VIBRATOR_ID).isVibrating(),
                 TEST_TIMEOUT_MILLIS));
@@ -494,9 +495,9 @@
     public void vibrate_singleVibratorPrebaked_runsVibration() throws Exception {
         mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_THUD);
 
-        long vibrationId = 1;
         VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_THUD);
-        startThreadAndDispatcher(vibrationId, effect);
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
         waitForCompletion();
 
         verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L));
@@ -514,12 +515,12 @@
             throws Exception {
         mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
 
-        long vibrationId = 1;
         VibrationEffect fallback = VibrationEffect.createOneShot(10, 100);
-        HalVibration vibration = createVibration(vibrationId, CombinedVibration.createParallel(
+        HalVibration vibration = createVibration(CombinedVibration.createParallel(
                 VibrationEffect.get(VibrationEffect.EFFECT_CLICK)));
         vibration.addFallback(VibrationEffect.EFFECT_CLICK, fallback);
-        startThreadAndDispatcher(vibration);
+        VibrationStepConductor conductor = startThreadAndDispatcher(vibration);
+        long vibrationId = conductor.getVibration().id;
         waitForCompletion();
 
         verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L));
@@ -536,9 +537,9 @@
     @Test
     public void vibrate_singleVibratorPrebakedAndUnsupportedEffect_ignoresVibration()
             throws Exception {
-        long vibrationId = 1;
         VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
-        startThreadAndDispatcher(vibrationId, effect);
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
         waitForCompletion();
 
         verify(mManagerHooks).noteVibratorOn(eq(UID), eq(0L));
@@ -555,12 +556,12 @@
         fakeVibrator.setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK,
                 VibrationEffect.Composition.PRIMITIVE_TICK);
 
-        long vibrationId = 1;
         VibrationEffect effect = VibrationEffect.startComposition()
                 .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
                 .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f)
                 .compose();
-        startThreadAndDispatcher(vibrationId, effect);
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
         waitForCompletion();
 
         verify(mManagerHooks).noteVibratorOn(eq(UID), eq(40L));
@@ -576,11 +577,11 @@
 
     @Test
     public void vibrate_singleVibratorComposedAndNoCapability_ignoresVibration() {
-        long vibrationId = 1;
         VibrationEffect effect = VibrationEffect.startComposition()
                 .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
                 .compose();
-        startThreadAndDispatcher(vibrationId, effect);
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
         waitForCompletion();
 
         verify(mManagerHooks).noteVibratorOn(eq(UID), eq(0L));
@@ -600,13 +601,13 @@
                 VibrationEffect.Composition.PRIMITIVE_SPIN);
         fakeVibrator.setCompositionSizeMax(2);
 
-        long vibrationId = 1;
         VibrationEffect effect = VibrationEffect.startComposition()
                 .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
                 .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f)
                 .addPrimitive(VibrationEffect.Composition.PRIMITIVE_SPIN, 0.8f)
                 .compose();
-        startThreadAndDispatcher(vibrationId, effect);
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
         waitForCompletion();
 
         verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
@@ -630,7 +631,6 @@
         fakeVibrator.setMaxAmplitudes(
                 0.5f /* 100Hz*/, 1 /* 150Hz */, 0.6f /* 200Hz */);
 
-        long vibrationId = 1;
         VibrationEffect effect = VibrationEffect.startComposition()
                 .addEffect(VibrationEffect.createOneShot(10, 100))
                 .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
@@ -643,7 +643,8 @@
                         .build())
                 .addEffect(VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
                 .compose();
-        startThreadAndDispatcher(vibrationId, effect);
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
         waitForCompletion();
 
         // Use first duration the vibrator is turned on since we cannot estimate the clicks.
@@ -675,7 +676,6 @@
                 VibrationEffect.Composition.PRIMITIVE_TICK);
         fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
 
-        long vibrationId = 1;
         VibrationEffect fallback = VibrationEffect.createOneShot(10, 100);
         VibrationEffect effect = VibrationEffect.startComposition()
                 .addEffect(VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
@@ -683,9 +683,10 @@
                 .addEffect(VibrationEffect.get(VibrationEffect.EFFECT_TICK))
                 .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f)
                 .compose();
-        HalVibration vib = createVibration(vibrationId, CombinedVibration.createParallel(effect));
+        HalVibration vib = createVibration(CombinedVibration.createParallel(effect));
         vib.addFallback(VibrationEffect.EFFECT_TICK, fallback);
-        startThreadAndDispatcher(vib);
+        VibrationStepConductor conductor = startThreadAndDispatcher(vib);
+        long vibrationId = conductor.getVibration().id;
         waitForCompletion();
 
         // Use first duration the vibrator is turned on since we cannot estimate the clicks.
@@ -718,7 +719,6 @@
         fakeVibrator.setMaxAmplitudes(
                 0.5f /* 100Hz*/, 1 /* 150Hz */, 0.6f /* 200Hz */);
 
-        long vibrationId = 1;
         VibrationEffect effect = VibrationEffect.startWaveform(targetAmplitude(1))
                 .addSustain(Duration.ofMillis(10))
                 .addTransition(Duration.ofMillis(20), targetAmplitude(0))
@@ -726,7 +726,8 @@
                 .addSustain(Duration.ofMillis(30))
                 .addTransition(Duration.ofMillis(40), targetAmplitude(0.6f), targetFrequency(200))
                 .build();
-        startThreadAndDispatcher(vibrationId, effect);
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
         waitForCompletion();
 
         verify(mManagerHooks).noteVibratorOn(eq(UID), eq(100L));
@@ -756,7 +757,6 @@
         fakeVibrator.setMaxAmplitudes(1, 1, 1);
         fakeVibrator.setPwleSizeMax(3);
 
-        long vibrationId = 1;
         VibrationEffect effect = VibrationEffect.startWaveform(targetAmplitude(1))
                 .addSustain(Duration.ofMillis(10))
                 .addTransition(Duration.ofMillis(20), targetAmplitude(0))
@@ -768,7 +768,8 @@
                 .addTransition(Duration.ofMillis(40), targetAmplitude(0.7f), targetFrequency(200))
                 .addTransition(Duration.ofMillis(40), targetAmplitude(0.6f), targetFrequency(200))
                 .build();
-        startThreadAndDispatcher(vibrationId, effect);
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
         waitForCompletion();
 
         verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
@@ -784,9 +785,9 @@
         FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
         fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
 
-        long vibrationId = 1;
         VibrationEffect effect = VibrationEffect.createWaveform(new long[]{5}, new int[]{100}, 0);
-        VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect);
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
 
         assertTrue(waitUntil(() -> fakeVibrator.getAmplitudes().size() > 2, TEST_TIMEOUT_MILLIS));
         // Vibration still running after 2 cycles.
@@ -804,9 +805,9 @@
     public void vibrate_singleVibrator_skipsSyncedCallbacks() {
         mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
 
-        long vibrationId = 1;
-        startThreadAndDispatcher(vibrationId,
+        VibrationStepConductor conductor = startThreadAndDispatcher(
                 VibrationEffect.createOneShot(10, 100));
+        long vibrationId = conductor.getVibration().id;
         waitForCompletion();
 
         verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
@@ -820,12 +821,12 @@
             throws Exception {
         mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_TICK);
 
-        long vibrationId = 1;
         CombinedVibration effect = CombinedVibration.startParallel()
                 .addVibrator(VIBRATOR_ID, VibrationEffect.get(VibrationEffect.EFFECT_TICK))
                 .addVibrator(2, VibrationEffect.get(VibrationEffect.EFFECT_TICK))
                 .combine();
-        startThreadAndDispatcher(vibrationId, effect);
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
         waitForCompletion();
 
         verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L));
@@ -846,10 +847,10 @@
         mVibratorProviders.get(2).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
         mVibratorProviders.get(3).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
 
-        long vibrationId = 1;
         CombinedVibration effect = CombinedVibration.createParallel(
                 VibrationEffect.get(VibrationEffect.EFFECT_CLICK));
-        startThreadAndDispatcher(vibrationId, effect);
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
         waitForCompletion();
 
         verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L));
@@ -881,7 +882,6 @@
         mVibratorProviders.get(4).setSupportedPrimitives(
                 VibrationEffect.Composition.PRIMITIVE_CLICK);
 
-        long vibrationId = 1;
         VibrationEffect composed = VibrationEffect.startComposition()
                 .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
                 .compose();
@@ -892,7 +892,8 @@
                         new long[]{10, 10}, new int[]{1, 2}, -1))
                 .addVibrator(4, composed)
                 .combine();
-        startThreadAndDispatcher(vibrationId, effect);
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
         waitForCompletion();
 
         verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L));
@@ -929,7 +930,6 @@
                 VibrationEffect.Composition.PRIMITIVE_CLICK);
         mVibratorProviders.get(3).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
 
-        long vibrationId = 1;
         VibrationEffect composed = VibrationEffect.startComposition()
                 .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
                 .compose();
@@ -938,7 +938,8 @@
                 .addNext(1, VibrationEffect.createOneShot(10, 100), /* delay= */ 50)
                 .addNext(2, composed, /* delay= */ 50)
                 .combine();
-        startThreadAndDispatcher(vibrationId, effect);
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
 
         waitForCompletion();
         InOrder controllerVerifier = inOrder(mControllerCallbacks);
@@ -972,7 +973,6 @@
     @Test
     public void vibrate_multipleSyncedCallbackTriggered_finishSteps() throws Exception {
         int[] vibratorIds = new int[]{1, 2};
-        long vibrationId = 1;
         mockVibrators(vibratorIds);
         mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
         mVibratorProviders.get(1).setSupportedPrimitives(
@@ -981,13 +981,15 @@
         mVibratorProviders.get(2).setSupportedPrimitives(
                 VibrationEffect.Composition.PRIMITIVE_CLICK);
         when(mManagerHooks.prepareSyncedVibration(anyLong(), eq(vibratorIds))).thenReturn(true);
-        when(mManagerHooks.triggerSyncedVibration(eq(vibrationId))).thenReturn(true);
 
         VibrationEffect composed = VibrationEffect.startComposition()
                 .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 100)
                 .compose();
         CombinedVibration effect = CombinedVibration.createParallel(composed);
-        VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect);
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
+
+        when(mManagerHooks.triggerSyncedVibration(eq(vibrationId))).thenReturn(true);
 
         assertTrue(waitUntil(
                 () -> !mVibratorProviders.get(1).getEffectSegments(vibrationId).isEmpty()
@@ -1021,7 +1023,6 @@
         when(mManagerHooks.prepareSyncedVibration(anyLong(), any())).thenReturn(true);
         when(mManagerHooks.triggerSyncedVibration(anyLong())).thenReturn(true);
 
-        long vibrationId = 1;
         VibrationEffect composed = VibrationEffect.startComposition()
                 .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
                 .compose();
@@ -1031,7 +1032,8 @@
                 .addVibrator(3, VibrationEffect.createWaveform(new long[]{10}, new int[]{100}, -1))
                 .addVibrator(4, composed)
                 .combine();
-        startThreadAndDispatcher(vibrationId, effect);
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
         waitForCompletion();
 
         long expectedCap = IVibratorManager.CAP_SYNC
@@ -1055,12 +1057,12 @@
         mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
         when(mManagerHooks.prepareSyncedVibration(anyLong(), any())).thenReturn(false);
 
-        long vibrationId = 1;
         CombinedVibration effect = CombinedVibration.startParallel()
                 .addVibrator(1, VibrationEffect.createOneShot(10, 100))
                 .addVibrator(2, VibrationEffect.createWaveform(new long[]{5}, new int[]{200}, -1))
                 .combine();
-        startThreadAndDispatcher(vibrationId, effect);
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
         waitForCompletion();
 
         long expectedCap = IVibratorManager.CAP_SYNC | IVibratorManager.CAP_PREPARE_ON;
@@ -1084,12 +1086,12 @@
         when(mManagerHooks.prepareSyncedVibration(anyLong(), any())).thenReturn(true);
         when(mManagerHooks.triggerSyncedVibration(anyLong())).thenReturn(false);
 
-        long vibrationId = 1;
         CombinedVibration effect = CombinedVibration.startParallel()
                 .addVibrator(1, VibrationEffect.createOneShot(10, 100))
                 .addVibrator(2, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
                 .combine();
-        startThreadAndDispatcher(vibrationId, effect);
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
         waitForCompletion();
 
         long expectedCap = IVibratorManager.CAP_SYNC
@@ -1110,7 +1112,6 @@
         mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
         mVibratorProviders.get(3).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
 
-        long vibrationId = 1;
         CombinedVibration effect = CombinedVibration.startParallel()
                 .addVibrator(1, VibrationEffect.createWaveform(
                         new long[]{5, 10, 10}, new int[]{1, 2, 3}, -1))
@@ -1119,7 +1120,8 @@
                 .addVibrator(3, VibrationEffect.createWaveform(
                         new long[]{60}, new int[]{6}, -1))
                 .combine();
-        startThreadAndDispatcher(vibrationId, effect);
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
 
         // All vibrators are turned on in parallel.
         assertTrue(waitUntil(
@@ -1169,8 +1171,7 @@
         Arrays.fill(amplitudes, VibrationEffect.DEFAULT_AMPLITUDE);
         VibrationEffect effect = VibrationEffect.createWaveform(timings, amplitudes, -1);
 
-        long vibrationId = 1;
-        startThreadAndDispatcher(vibrationId, effect);
+        startThreadAndDispatcher(effect);
         long startTime = SystemClock.elapsedRealtime();
 
         waitForCompletion(totalDuration + TEST_TIMEOUT_MILLIS);
@@ -1192,9 +1193,9 @@
         long latency = 5_000; // 5s
         fakeVibrator.setOnLatency(latency);
 
-        long vibrationId = 1;
         VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
-        VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect);
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
 
         assertTrue(waitUntil(() -> !fakeVibrator.getEffectSegments(vibrationId).isEmpty(),
                 TEST_TIMEOUT_MILLIS));
@@ -1226,7 +1227,6 @@
         mVibratorProviders.get(2).setSupportedPrimitives(
                 VibrationEffect.Composition.PRIMITIVE_CLICK);
 
-        long vibrationId = 1;
         CombinedVibration effect = CombinedVibration.startParallel()
                 .addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
                 .addVibrator(2, VibrationEffect.startComposition()
@@ -1235,7 +1235,8 @@
                         .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100)
                         .compose())
                 .combine();
-        VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect);
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
 
         assertTrue(waitUntil(() -> mControllers.get(2).isVibrating(),
                 TEST_TIMEOUT_MILLIS));
@@ -1264,13 +1265,13 @@
         mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
         mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
 
-        long vibrationId = 1;
         CombinedVibration effect = CombinedVibration.startParallel()
                 .addVibrator(1, VibrationEffect.createWaveform(
                         new long[]{100, 100}, new int[]{1, 2}, 0))
                 .addVibrator(2, VibrationEffect.createOneShot(100, 100))
                 .combine();
-        VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect);
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
 
         assertTrue(waitUntil(() -> mControllers.get(1).isVibrating()
                         && mControllers.get(2).isVibrating(),
@@ -1296,9 +1297,9 @@
 
     @Test
     public void vibrate_binderDied_cancelsVibration() throws Exception {
-        long vibrationId = 1;
         VibrationEffect effect = VibrationEffect.createWaveform(new long[]{5}, new int[]{100}, 0);
-        VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect);
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
 
         assertTrue(waitUntil(() -> mControllers.get(VIBRATOR_ID).isVibrating(),
                 TEST_TIMEOUT_MILLIS));
@@ -1320,10 +1321,10 @@
         mEffectAdapter = new DeviceVibrationEffectAdapter(mVibrationSettings);
         mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
 
-        long vibrationId = 1;
         VibrationEffect effect = VibrationEffect.createWaveform(
                 new long[]{5, 5, 5}, new int[]{60, 120, 240}, -1);
-        startThreadAndDispatcher(vibrationId, effect);
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
         waitForCompletion();
 
         verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
@@ -1346,9 +1347,9 @@
         mEffectAdapter = new DeviceVibrationEffectAdapter(mVibrationSettings);
         mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
 
-        long vibrationId = 1;
         VibrationEffect effect = VibrationEffect.createOneShot(10, 200);
-        VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect);
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
 
         // Vibration completed but vibrator not yet released.
         verify(mManagerHooks, timeout(TEST_TIMEOUT_MILLIS)).onVibrationCompleted(eq(vibrationId),
@@ -1381,9 +1382,9 @@
         mEffectAdapter = new DeviceVibrationEffectAdapter(mVibrationSettings);
         mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
 
-        long vibrationId = 1;
         VibrationEffect effect = VibrationEffect.createOneShot(10_000, 240);
-        VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect);
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
         assertTrue(waitUntil(() -> mControllers.get(VIBRATOR_ID).isVibrating(),
                 TEST_TIMEOUT_MILLIS));
         conductor.notifyCancelled(
@@ -1410,9 +1411,9 @@
         mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
         mVibratorProviders.get(VIBRATOR_ID).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
 
-        long vibrationId = 1;
         VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
-        startThreadAndDispatcher(vibrationId, effect);
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
         waitForCompletion();
 
         verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
@@ -1432,11 +1433,11 @@
         mVibratorProviders.get(VIBRATOR_ID).setSupportedPrimitives(
                 VibrationEffect.Composition.PRIMITIVE_CLICK);
 
-        long vibrationId = 1;
         VibrationEffect effect = VibrationEffect.startComposition()
                 .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
                 .compose();
-        startThreadAndDispatcher(vibrationId, effect);
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
         waitForCompletion();
 
         verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
@@ -1461,11 +1462,11 @@
         fakeVibrator.setMaxAmplitudes(1, 1, 1);
         fakeVibrator.setPwleSizeMax(2);
 
-        long vibrationId = 1;
         VibrationEffect effect = VibrationEffect.startWaveform()
                 .addTransition(Duration.ofMillis(1), targetAmplitude(1))
                 .build();
-        startThreadAndDispatcher(vibrationId, effect);
+        VibrationStepConductor conductor = startThreadAndDispatcher(effect);
+        long vibrationId = conductor.getVibration().id;
         waitForCompletion();
 
         verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
@@ -1485,12 +1486,6 @@
         mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL,
                 IVibrator.CAP_COMPOSE_EFFECTS);
 
-        long vibrationId1 = 1;
-        long vibrationId2 = 2;
-        long vibrationId3 = 3;
-        long vibrationId4 = 4;
-        long vibrationId5 = 5;
-
         // A simple effect, followed by a repeating effect that gets cancelled, followed by another
         // simple effect.
         VibrationEffect effect1 = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
@@ -1503,12 +1498,14 @@
         VibrationEffect effect4 = VibrationEffect.createOneShot(8000, 100);
         VibrationEffect effect5 = VibrationEffect.createOneShot(20, 222);
 
-        startThreadAndDispatcher(vibrationId1, effect1);
+        VibrationStepConductor conductor1 = startThreadAndDispatcher(effect1);
+        long vibrationId1 = conductor1.getVibration().id;
         waitForCompletion();
         verify(mControllerCallbacks).onComplete(VIBRATOR_ID, vibrationId1);
         verifyCallbacksTriggered(vibrationId1, Vibration.Status.FINISHED);
 
-        VibrationStepConductor conductor2 = startThreadAndDispatcher(vibrationId2, effect2);
+        VibrationStepConductor conductor2 = startThreadAndDispatcher(effect2);
+        long vibrationId2 = conductor2.getVibration().id;
         // Effect2 won't complete on its own. Cancel it after a couple of repeats.
         Thread.sleep(150);  // More than two TICKs.
         conductor2.notifyCancelled(
@@ -1516,12 +1513,14 @@
                 /* immediate= */ false);
         waitForCompletion();
 
-        startThreadAndDispatcher(vibrationId3, effect3);
+        VibrationStepConductor conductor3 = startThreadAndDispatcher(effect3);
+        long vibrationId3 = conductor3.getVibration().id;
         waitForCompletion();
 
         // Effect4 is a long oneshot, but it gets cancelled as fast as possible.
         long start4 = System.currentTimeMillis();
-        VibrationStepConductor conductor4 = startThreadAndDispatcher(vibrationId4, effect4);
+        VibrationStepConductor conductor4 = startThreadAndDispatcher(effect4);
+        long vibrationId4 = conductor4.getVibration().id;
         conductor4.notifyCancelled(
                 new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SCREEN_OFF),
                 /* immediate= */ true);
@@ -1529,7 +1528,8 @@
         long duration4 = System.currentTimeMillis() - start4;
 
         // Effect5 is to show that things keep going after the immediate cancel.
-        startThreadAndDispatcher(vibrationId5, effect5);
+        VibrationStepConductor conductor5 = startThreadAndDispatcher(effect5);
+        long vibrationId5 = conductor5.getVibration().id;
         waitForCompletion();
 
         FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
@@ -1582,14 +1582,12 @@
         }
     }
 
-    private VibrationStepConductor startThreadAndDispatcher(
-            long vibrationId, VibrationEffect effect) {
-        return startThreadAndDispatcher(vibrationId, CombinedVibration.createParallel(effect));
+    private VibrationStepConductor startThreadAndDispatcher(VibrationEffect effect) {
+        return startThreadAndDispatcher(CombinedVibration.createParallel(effect));
     }
 
-    private VibrationStepConductor startThreadAndDispatcher(long vibrationId,
-            CombinedVibration effect) {
-        return startThreadAndDispatcher(createVibration(vibrationId, effect));
+    private VibrationStepConductor startThreadAndDispatcher(CombinedVibration effect) {
+        return startThreadAndDispatcher(createVibration(effect));
     }
 
     private VibrationStepConductor startThreadAndDispatcher(HalVibration vib) {
@@ -1624,9 +1622,9 @@
         mTestLooper.dispatchAll();  // Flush callbacks
     }
 
-    private HalVibration createVibration(long id, CombinedVibration effect) {
-        return new HalVibration(mVibrationToken, (int) id, effect, ATTRS, UID, DISPLAY_ID,
-                PACKAGE_NAME, "reason");
+    private HalVibration createVibration(CombinedVibration effect) {
+        return new HalVibration(mVibrationToken, effect,
+                new Vibration.CallerInfo(ATTRS, UID, DISPLAY_ID, PACKAGE_NAME, "reason"));
     }
 
     private SparseArray<VibratorController> createVibratorControllers() {
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 38c2f40..c31c23f 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -19,6 +19,7 @@
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.app.Notification.EXTRA_ALLOW_DURING_SETUP;
 import static android.app.Notification.FLAG_AUTO_CANCEL;
 import static android.app.Notification.FLAG_BUBBLE;
 import static android.app.Notification.FLAG_CAN_COLORIZE;
@@ -71,6 +72,7 @@
 import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL;
 import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
 
+import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.ALLOW_DISMISS_ONGOING;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -201,6 +203,7 @@
 
 import com.android.internal.app.IAppOpsService;
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
+import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
 import com.android.internal.logging.InstanceIdSequence;
 import com.android.internal.logging.InstanceIdSequenceFake;
 import com.android.internal.messages.nano.SystemMessageProto;
@@ -373,6 +376,9 @@
     BroadcastReceiver mPackageIntentReceiver;
     NotificationRecordLoggerFake mNotificationRecordLogger = new NotificationRecordLoggerFake();
     TestableNotificationManagerService.StrongAuthTrackerFake mStrongAuthTracker;
+
+    TestFlagResolver mTestFlagResolver = new TestFlagResolver();
+
     private InstanceIdSequence mNotificationInstanceIdSequence = new InstanceIdSequenceFake(
             1 << 30);
     @Mock
@@ -521,7 +527,7 @@
                 mAppOpsManager, mAppOpsService, mUm, mHistoryManager, mStatsManager,
                 mock(TelephonyManager.class),
                 mAmi, mToastRateLimiter, mPermissionHelper, mock(UsageStatsManagerInternal.class),
-                mTelecomManager, mLogger);
+                mTelecomManager, mLogger, mTestFlagResolver);
         // Return first true for RoleObserver main-thread check
         when(mMainLooper.isCurrentThread()).thenReturn(true).thenReturn(false);
         mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY, mMainLooper);
@@ -1594,6 +1600,8 @@
 
     @Test
     public void testEnqueueNotificationWithTag_FgsAddsFlags_dismissalAllowed() throws Exception {
+        mContext.getTestablePermissions().setPermission(
+                android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED);
         DeviceConfig.setProperty(
                 DeviceConfig.NAMESPACE_SYSTEMUI,
                 SystemUiDeviceConfigFlags.TASK_MANAGER_ENABLED,
@@ -1622,6 +1630,8 @@
 
     @Test
     public void testEnqueueNotificationWithTag_FGSaddsFlags_dismissalNotAllowed() throws Exception {
+        mContext.getTestablePermissions().setPermission(
+                android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED);
         DeviceConfig.setProperty(
                 DeviceConfig.NAMESPACE_SYSTEMUI,
                 SystemUiDeviceConfigFlags.TASK_MANAGER_ENABLED,
@@ -4211,8 +4221,35 @@
     }
 
     @Test
+    public void testNoNotificationDuringSetupPermission() throws Exception {
+        mContext.getTestablePermissions().setPermission(
+                android.Manifest.permission.NOTIFICATION_DURING_SETUP, PERMISSION_GRANTED);
+        Bundle extras = new Bundle();
+        extras.putBoolean(EXTRA_ALLOW_DURING_SETUP, true);
+        Notification.Builder nb = new Notification.Builder(mContext,
+                mTestNotificationChannel.getId())
+                .setContentTitle("foo")
+                .addExtras(extras)
+                .setSmallIcon(android.R.drawable.sym_def_app_icon);
+        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
+                "testNoNotificationDuringSetupPermission", mUid, 0,
+                nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
+        NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(),
+                nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
+        waitForIdle();
+
+        NotificationRecord posted = mService.findNotificationLocked(
+                PKG, nr.getSbn().getTag(), nr.getSbn().getId(), nr.getSbn().getUserId());
+
+        assertTrue(posted.getNotification().extras.containsKey(EXTRA_ALLOW_DURING_SETUP));
+    }
+
+    @Test
     public void testNoFakeColorizedPermission() throws Exception {
-        when(mPackageManagerClient.checkPermission(any(), any())).thenReturn(PERMISSION_DENIED);
+        mContext.getTestablePermissions().setPermission(
+                android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_DENIED);
         Notification.Builder nb = new Notification.Builder(mContext,
                 mTestNotificationChannel.getId())
                 .setContentTitle("foo")
@@ -4237,9 +4274,8 @@
     @Test
     public void testMediaStyleRemote_hasPermission() throws RemoteException {
         String deviceName = "device";
-        when(mPackageManager.checkPermission(
-                eq(android.Manifest.permission.MEDIA_CONTENT_CONTROL), any(), anyInt()))
-                .thenReturn(PERMISSION_GRANTED);
+        mContext.getTestablePermissions().setPermission(
+                android.Manifest.permission.MEDIA_CONTENT_CONTROL, PERMISSION_GRANTED);
         Notification.MediaStyle style = new Notification.MediaStyle();
         style.setRemotePlaybackInfo(deviceName, 0, null);
         Notification.Builder nb = new Notification.Builder(mContext,
@@ -4266,9 +4302,8 @@
     @Test
     public void testMediaStyleRemote_noPermission() throws RemoteException {
         String deviceName = "device";
-        when(mPackageManager.checkPermission(
-                eq(android.Manifest.permission.MEDIA_CONTENT_CONTROL), any(), anyInt()))
-                .thenReturn(PERMISSION_DENIED);
+        mContext.getTestablePermissions().setPermission(
+                android.Manifest.permission.MEDIA_CONTENT_CONTROL, PERMISSION_DENIED);
         Notification.MediaStyle style = new Notification.MediaStyle();
         style.setRemotePlaybackInfo(deviceName, 0, null);
         Notification.Builder nb = new Notification.Builder(mContext,
@@ -4294,9 +4329,8 @@
     @Test
     public void testSubstituteAppName_hasPermission() throws RemoteException {
         String subName = "Substitute Name";
-        when(mPackageManager.checkPermission(
-                eq(android.Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME), any(), anyInt()))
-                .thenReturn(PERMISSION_GRANTED);
+        mContext.getTestablePermissions().setPermission(
+                android.Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME, PERMISSION_GRANTED);
         Bundle extras = new Bundle();
         extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, subName);
         Notification.Builder nb = new Notification.Builder(mContext,
@@ -4321,9 +4355,8 @@
 
     @Test
     public void testSubstituteAppName_noPermission() throws RemoteException {
-        when(mPackageManager.checkPermission(
-                eq(android.Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME), any(), anyInt()))
-                .thenReturn(PERMISSION_DENIED);
+        mContext.getTestablePermissions().setPermission(
+                android.Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME, PERMISSION_DENIED);
         Bundle extras = new Bundle();
         extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, "Substitute Name");
         Notification.Builder nb = new Notification.Builder(mContext,
@@ -8290,7 +8323,7 @@
         assertNotNull(n.publicVersion.bigContentView);
         assertNotNull(n.publicVersion.headsUpContentView);
 
-        mService.fixNotification(n, PKG, "tag", 9, 0);
+        mService.fixNotification(n, PKG, "tag", 9, 0, mUid);
 
         assertNull(n.contentView);
         assertNull(n.bigContentView);
@@ -10092,8 +10125,8 @@
             throws Exception {
         // Given: a notification from an app on the system partition has the flag
         // FLAG_ONGOING_EVENT set
-        // feature flag: NOTIFICATION_ONGOING_DISMISSAL is on
-        mService.setOngoingDismissal(true);
+        // feature flag: ALLOW_DISMISS_ONGOING is on
+        mTestFlagResolver.setFlagOverride(ALLOW_DISMISS_ONGOING, true);
         Notification n = new Notification.Builder(mContext, "test")
                 .setOngoing(true)
                 .build();
@@ -10104,21 +10137,18 @@
                 .thenReturn(systemAppInfo);
 
         // When: fix the notification with NotificationManagerService
-        mService.fixNotification(n, PKG, "tag", 9, 0);
+        mService.fixNotification(n, PKG, "tag", 9, 0, mUid);
 
         // Then: the notification's flag FLAG_NO_DISMISS should be set
         assertNotSame(0, n.flags & Notification.FLAG_NO_DISMISS);
-
-        // Avoid affecting other tests
-        mService.setOngoingDismissal(false);
     }
 
     @Test
     public void fixMediaNotification_withOnGoingFlag_shouldBeNonDismissible()
             throws Exception {
         // Given: a media notification has the flag FLAG_ONGOING_EVENT set
-        // feature flag: NOTIFICATION_ONGOING_DISMISSAL is on
-        mService.setOngoingDismissal(true);
+        // feature flag: ALLOW_DISMISS_ONGOING is on
+        mTestFlagResolver.setFlagOverride(ALLOW_DISMISS_ONGOING, true);
         Notification n = new Notification.Builder(mContext, "test")
                 .setOngoing(true)
                 .setStyle(new Notification.MediaStyle()
@@ -10126,32 +10156,26 @@
                 .build();
 
         // When: fix the notification with NotificationManagerService
-        mService.fixNotification(n, PKG, "tag", 9, 0);
+        mService.fixNotification(n, PKG, "tag", 9, 0, mUid);
 
         // Then: the notification's flag FLAG_NO_DISMISS should be set
         assertNotSame(0, n.flags & Notification.FLAG_NO_DISMISS);
-
-        // Avoid affecting other tests
-        mService.setOngoingDismissal(false);
     }
 
     @Test
     public void fixNonExemptNotification_withOnGoingFlag_shouldBeDismissible() throws Exception {
         // Given: a non-exempt notification has the flag FLAG_ONGOING_EVENT set
-        // feature flag: NOTIFICATION_ONGOING_DISMISSAL is on
-        mService.setOngoingDismissal(true);
+        // feature flag: ALLOW_DISMISS_ONGOING is on
+        mTestFlagResolver.setFlagOverride(ALLOW_DISMISS_ONGOING, true);
         Notification n = new Notification.Builder(mContext, "test")
                 .setOngoing(true)
                 .build();
 
         // When: fix the notification with NotificationManagerService
-        mService.fixNotification(n, PKG, "tag", 9, 0);
+        mService.fixNotification(n, PKG, "tag", 9, 0, mUid);
 
         // Then: the notification's flag FLAG_NO_DISMISS should not be set
         assertEquals(0, n.flags & Notification.FLAG_NO_DISMISS);
-
-        // Avoid affecting other tests
-        mService.setOngoingDismissal(false);
     }
 
     @Test
@@ -10159,28 +10183,25 @@
             throws Exception {
         // Given: a non-exempt notification has the flag FLAG_NO_DISMISS set (even though this is
         // not allowed)
-        // feature flag: NOTIFICATION_ONGOING_DISMISSAL is on
-        mService.setOngoingDismissal(true);
+        // feature flag: ALLOW_DISMISS_ONGOING is on
+        mTestFlagResolver.setFlagOverride(ALLOW_DISMISS_ONGOING, true);
         Notification n = new Notification.Builder(mContext, "test")
                 .build();
         n.flags |= Notification.FLAG_NO_DISMISS;
 
         // When: fix the notification with NotificationManagerService
-        mService.fixNotification(n, PKG, "tag", 9, 0);
+        mService.fixNotification(n, PKG, "tag", 9, 0, mUid);
 
         // Then: the notification's flag FLAG_NO_DISMISS should be cleared
         assertEquals(0, n.flags & Notification.FLAG_NO_DISMISS);
-
-        // Avoid affecting other tests
-        mService.setOngoingDismissal(false);
     }
 
     @Test
     public void fixSystemNotification_withoutOnGoingFlag_shouldBeDismissible() throws Exception {
         // Given: a notification from an app on the system partition doesn't have the flag
         // FLAG_ONGOING_EVENT set
-        // feature flag: NOTIFICATION_ONGOING_DISMISSAL is on
-        mService.setOngoingDismissal(true);
+        // feature flag: ALLOW_DISMISS_ONGOING is on
+        mTestFlagResolver.setFlagOverride(ALLOW_DISMISS_ONGOING, true);
         Notification n = new Notification.Builder(mContext, "test")
                 .setOngoing(false)
                 .build();
@@ -10191,13 +10212,10 @@
                 .thenReturn(systemAppInfo);
 
         // When: fix the notification with NotificationManagerService
-        mService.fixNotification(n, PKG, "tag", 9, 0);
+        mService.fixNotification(n, PKG, "tag", 9, 0, mUid);
 
         // Then: the notification's flag FLAG_NO_DISMISS should not be set
         assertEquals(0, n.flags & Notification.FLAG_NO_DISMISS);
-
-        // Avoid affecting other tests
-        mService.setOngoingDismissal(false);
     }
 
     @Test
@@ -10205,8 +10223,8 @@
             throws Exception {
         // Given: a notification from an app on the system partition doesn't have the flag
         // FLAG_ONGOING_EVENT set, but has the flag FLAG_NO_DISMISS set
-        // feature flag: NOTIFICATION_ONGOING_DISMISSAL is on
-        mService.setOngoingDismissal(true);
+        // feature flag: ALLOW_DISMISS_ONGOING is on
+        mTestFlagResolver.setFlagOverride(ALLOW_DISMISS_ONGOING, true);
         Notification n = new Notification.Builder(mContext, "test")
                 .setOngoing(false)
                 .build();
@@ -10218,20 +10236,17 @@
                 .thenReturn(systemAppInfo);
 
         // When: fix the notification with NotificationManagerService
-        mService.fixNotification(n, PKG, "tag", 9, 0);
+        mService.fixNotification(n, PKG, "tag", 9, 0, mUid);
 
         // Then: the notification's flag FLAG_NO_DISMISS should be cleared
         assertEquals(0, n.flags & Notification.FLAG_NO_DISMISS);
-
-        // Avoid affecting other tests
-        mService.setOngoingDismissal(false);
     }
 
     @Test
     public void fixMediaNotification_withoutOnGoingFlag_shouldBeDismissible() throws Exception {
         // Given: a media notification doesn't have the flag FLAG_ONGOING_EVENT set
-        // feature flag: NOTIFICATION_ONGOING_DISMISSAL is on
-        mService.setOngoingDismissal(true);
+        // feature flag: ALLOW_DISMISS_ONGOING is on
+        mTestFlagResolver.setFlagOverride(ALLOW_DISMISS_ONGOING, true);
         Notification n = new Notification.Builder(mContext, "test")
                 .setOngoing(false)
                 .setStyle(new Notification.MediaStyle()
@@ -10239,13 +10254,10 @@
                 .build();
 
         // When: fix the notification with NotificationManagerService
-        mService.fixNotification(n, PKG, "tag", 9, 0);
+        mService.fixNotification(n, PKG, "tag", 9, 0, mUid);
 
         // Then: the notification's flag FLAG_NO_DISMISS should not be set
         assertEquals(0, n.flags & Notification.FLAG_NO_DISMISS);
-
-        // Avoid affecting other tests
-        mService.setOngoingDismissal(false);
     }
 
     @Test
@@ -10253,8 +10265,8 @@
             throws Exception {
         // Given: a media notification doesn't have the flag FLAG_ONGOING_EVENT set,
         // but has the flag FLAG_NO_DISMISS set
-        // feature flag: NOTIFICATION_ONGOING_DISMISSAL is on
-        mService.setOngoingDismissal(true);
+        // feature flag: ALLOW_DISMISS_ONGOING is on
+        mTestFlagResolver.setFlagOverride(ALLOW_DISMISS_ONGOING, true);
         Notification n = new Notification.Builder(mContext, "test")
                 .setOngoing(false)
                 .setStyle(new Notification.MediaStyle()
@@ -10263,34 +10275,27 @@
         n.flags |= Notification.FLAG_NO_DISMISS;
 
         // When: fix the notification with NotificationManagerService
-        mService.fixNotification(n, PKG, "tag", 9, 0);
+        mService.fixNotification(n, PKG, "tag", 9, 0, mUid);
 
         // Then: the notification's flag FLAG_NO_DISMISS should be cleared
         assertEquals(0, n.flags & Notification.FLAG_NO_DISMISS);
-
-        // Avoid affecting other tests
-        mService.setOngoingDismissal(false);
     }
 
     @Test
     public void fixNonExempt_Notification_withoutOnGoingFlag_shouldBeDismissible()
             throws Exception {
         // Given: a non-exempt notification has the flag FLAG_ONGOING_EVENT set
-        // feature flag: NOTIFICATION_ONGOING_DISMISSAL is on
-        mService.setOngoingDismissal(true);
-        mService.setOngoingDismissal(true);
+        // feature flag: ALLOW_DISMISS_ONGOING is on
+        mTestFlagResolver.setFlagOverride(ALLOW_DISMISS_ONGOING, true);
         Notification n = new Notification.Builder(mContext, "test")
                 .setOngoing(false)
                 .build();
 
         // When: fix the notification with NotificationManagerService
-        mService.fixNotification(n, PKG, "tag", 9, 0);
+        mService.fixNotification(n, PKG, "tag", 9, 0, mUid);
 
         // Then: the notification's flag FLAG_NO_DISMISS should not be set
         assertEquals(0, n.flags & Notification.FLAG_NO_DISMISS);
-
-        // Avoid affecting other tests
-        mService.setOngoingDismissal(false);
     }
 
     @Test
@@ -10298,21 +10303,18 @@
             throws Exception {
         when(mDevicePolicyManager.isActiveDeviceOwner(mUid)).thenReturn(true);
         // Given: a notification has the flag FLAG_ONGOING_EVENT set
-        // feature flag: NOTIFICATION_ONGOING_DISMISSAL is on
-        mService.setOngoingDismissal(true);
+        // feature flag: ALLOW_DISMISS_ONGOING is on
+        mTestFlagResolver.setFlagOverride(ALLOW_DISMISS_ONGOING, true);
         mService.setSystemExemptFromDismissal(false);
         Notification n = new Notification.Builder(mContext, "test")
                 .setOngoing(true)
                 .build();
 
         // When: fix the notification with NotificationManagerService
-        mService.fixNotification(n, PKG, "tag", 9, 0);
+        mService.fixNotification(n, PKG, "tag", 9, 0, mUid);
 
         // Then: the notification's flag FLAG_NO_DISMISS should be set
         assertNotSame(0, n.flags & Notification.FLAG_NO_DISMISS);
-
-        // Avoid affecting other tests
-        mService.setOngoingDismissal(false);
     }
 
     @Test
@@ -10322,21 +10324,19 @@
                 AppOpsManager.OP_SYSTEM_EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS, mUid,
                 PKG)).thenReturn(AppOpsManager.MODE_ALLOWED);
         // Given: a notification has the flag FLAG_ONGOING_EVENT set
-        // feature flag: NOTIFICATION_ONGOING_DISMISSAL is on
-        mService.setOngoingDismissal(true);
+        // feature flag: ALLOW_DISMISS_ONGOING is on
+        mTestFlagResolver.setFlagOverride(ALLOW_DISMISS_ONGOING, true);
         mService.setSystemExemptFromDismissal(true);
         Notification n = new Notification.Builder(mContext, "test")
                 .setOngoing(true)
                 .build();
 
         // When: fix the notification with NotificationManagerService
-        mService.fixNotification(n, PKG, "tag", 9, 0);
+        mService.fixNotification(n, PKG, "tag", 9, 0, mUid);
 
         // Then: the notification's flag FLAG_NO_DISMISS should be set
         assertNotSame(0, n.flags & Notification.FLAG_NO_DISMISS);
 
-        // Avoid affecting other tests
-        mService.setOngoingDismissal(false);
         mService.setSystemExemptFromDismissal(false);
     }
 
@@ -10347,20 +10347,17 @@
                 AppOpsManager.OP_SYSTEM_EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS, mUid,
                 PKG)).thenReturn(AppOpsManager.MODE_ALLOWED);
         // Given: a notification has the flag FLAG_ONGOING_EVENT set
-        // feature flag: NOTIFICATION_ONGOING_DISMISSAL is on
-        mService.setOngoingDismissal(true);
+        // feature flag: ALLOW_DISMISS_ONGOING is on
+        mTestFlagResolver.setFlagOverride(ALLOW_DISMISS_ONGOING, true);
         mService.setSystemExemptFromDismissal(false);
         Notification n = new Notification.Builder(mContext, "test")
                 .setOngoing(true)
                 .build();
 
         // When: fix the notification with NotificationManagerService
-        mService.fixNotification(n, PKG, "tag", 9, 0);
+        mService.fixNotification(n, PKG, "tag", 9, 0, mUid);
 
         // Then: the notification's flag FLAG_NO_DISMISS should not be set
         assertEquals(0, n.flags & Notification.FLAG_NO_DISMISS);
-
-        // Avoid affecting other tests
-        mService.setOngoingDismissal(false);
     }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
index 98c156e..6f2627a 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
@@ -48,7 +48,6 @@
 import android.content.Context;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageManagerInternal;
 import android.os.Looper;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -169,7 +168,7 @@
                     mock(ActivityManagerInternal.class),
                     mock(MultiRateLimiter.class), mock(PermissionHelper.class),
                     mock(UsageStatsManagerInternal.class), mock (TelecomManager.class),
-                    mock(NotificationChannelLogger.class));
+                    mock(NotificationChannelLogger.class), new TestFlagResolver());
         } catch (SecurityException e) {
             if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) {
                 throw e;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/TestFlagResolver.java b/services/tests/uiservicestests/src/com/android/server/notification/TestFlagResolver.java
new file mode 100644
index 0000000..3b9726e
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/notification/TestFlagResolver.java
@@ -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.server.notification;
+
+import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class TestFlagResolver implements SystemUiSystemPropertiesFlags.FlagResolver {
+    private Map<SystemUiSystemPropertiesFlags.Flag, Boolean> mOverrides = new HashMap<>();
+
+    @Override
+    public boolean isEnabled(SystemUiSystemPropertiesFlags.Flag flag) {
+        return mOverrides.getOrDefault(flag, flag.mDefaultValue);
+    }
+
+    public void setFlagOverride(SystemUiSystemPropertiesFlags.Flag flag, boolean isEnabled) {
+        mOverrides.put(flag, isEnabled);
+    }
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java b/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java
index 1306d57..dd6923d 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java
@@ -36,8 +36,6 @@
     int countLogSmartSuggestionsVisible = 0;
     Set<Integer> mChannelToastsSent = new HashSet<>();
 
-    public boolean ONGOING_DISMISSAL = false;
-
     String stringArrayResourceValue;
     @Nullable
     NotificationAssistantAccessGrantedCallback mNotificationAssistantAccessGrantedCallback;
@@ -162,11 +160,6 @@
         }
     }
 
-    // Mock SystemProperties
-    protected void setOngoingDismissal(boolean ongoingDismissal) {
-        ONGOING_DISMISSAL = ongoingDismissal;
-    }
-
     protected void setSystemExemptFromDismissal(boolean isOn) {
         mSystemExemptFromDismissal = isOn;
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 1ce8c61..bfd99fd 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -2020,13 +2020,13 @@
         assertEquals(origRot, dc.getConfiguration().windowConfiguration.getRotation());
 
         // Once transition starts, rotation is applied and transition shows DC rotating.
-        testPlayer.start();
+        testPlayer.startTransition();
         assertNotEquals(origRot, dc.getConfiguration().windowConfiguration.getRotation());
         assertNotNull(testPlayer.mLastReady);
+        assertTrue(testPlayer.mController.isPlaying());
         WindowContainerToken dcToken = dc.mRemoteToken.toWindowContainerToken();
         assertNotEquals(testPlayer.mLastReady.getChange(dcToken).getEndRotation(),
                 testPlayer.mLastReady.getChange(dcToken).getStartRotation());
-        assertTrue(testPlayer.mLastTransit.applyDisplayChangeIfNeeded());
         testPlayer.finish();
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
index 4954e89..c2b3783 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
@@ -147,6 +147,20 @@
     }
 
     @Test
+    public void testOpenedCameraInSplitScreen_orientationNotFixed_doNotShowToast() {
+        configureActivity(SCREEN_ORIENTATION_UNSPECIFIED);
+        spyOn(mTask);
+        spyOn(mDisplayRotationCompatPolicy);
+        doReturn(WINDOWING_MODE_MULTI_WINDOW).when(mActivity).getWindowingMode();
+        doReturn(WINDOWING_MODE_MULTI_WINDOW).when(mTask).getWindowingMode();
+
+        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+        verify(mDisplayRotationCompatPolicy, never()).showToast(
+                R.string.display_rotation_camera_compat_toast_in_split_screen);
+    }
+
+    @Test
     public void testOnScreenRotationAnimationFinished_treatmentNotEnabled_doNotShowToast() {
         when(mLetterboxConfiguration.isCameraCompatTreatmentEnabled(
                     /* checkDeviceConfig */ anyBoolean()))
@@ -172,7 +186,7 @@
     @Test
     public void testOnScreenRotationAnimationFinished_notFullscreen_doNotShowToast() {
         configureActivity(SCREEN_ORIENTATION_PORTRAIT);
-        doReturn(WINDOWING_MODE_MULTI_WINDOW).when(mActivity).getWindowingMode();
+        doReturn(true).when(mActivity).inMultiWindowMode();
         spyOn(mDisplayRotationCompatPolicy);
 
         mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
@@ -184,6 +198,18 @@
     }
 
     @Test
+    public void testOnScreenRotationAnimationFinished_orientationNotFixed_doNotShowToast() {
+        configureActivity(SCREEN_ORIENTATION_UNSPECIFIED);
+        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+        spyOn(mDisplayRotationCompatPolicy);
+
+        mDisplayRotationCompatPolicy.onScreenRotationAnimationFinished();
+
+        verify(mDisplayRotationCompatPolicy, never()).showToast(
+                R.string.display_rotation_camera_compat_toast_after_rotation);
+    }
+
+    @Test
     public void testOnScreenRotationAnimationFinished_showToast() {
         configureActivity(SCREEN_ORIENTATION_PORTRAIT);
         mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index fe7e04d..c1874a4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -260,6 +260,51 @@
     }
 
     @Test
+    public void testCheckOpaqueIsLetterboxedWhenStrategyIsApplied() {
+        mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true);
+        setUpDisplaySizeWithApp(2000, 1000);
+        prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
+        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+        // Translucent Activity
+        final ActivityRecord translucentActivity = new ActivityBuilder(mAtm)
+                .setLaunchedFromUid(mActivity.getUid())
+                .build();
+        doReturn(false).when(translucentActivity).fillsParent();
+        spyOn(mActivity);
+        mTask.addChild(translucentActivity);
+        verify(mActivity).isFinishing();
+    }
+
+    @Test
+    public void testTranslucentActivitiesWhenUnfolding() {
+        mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true);
+        setUpDisplaySizeWithApp(2800, 1400);
+        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+        mActivity.mWmService.mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(
+                1.0f /*letterboxVerticalPositionMultiplier*/);
+        prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
+        // We launch a transparent activity
+        final ActivityRecord translucentActivity = new ActivityBuilder(mAtm)
+                .setLaunchedFromUid(mActivity.getUid())
+                .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
+                .build();
+        doReturn(false).when(translucentActivity).fillsParent();
+        mTask.addChild(translucentActivity);
+
+        mTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+        spyOn(mActivity);
+
+        // Halffold
+        setFoldablePosture(translucentActivity, true /* isHalfFolded */, false /* isTabletop */);
+        verify(mActivity).recomputeConfiguration();
+        clearInvocations(mActivity);
+
+        // Unfold
+        setFoldablePosture(translucentActivity, false /* isHalfFolded */, false /* isTabletop */);
+        verify(mActivity).recomputeConfiguration();
+    }
+
+    @Test
     public void testRestartProcessIfVisible() {
         setUpDisplaySizeWithApp(1000, 2500);
         doNothing().when(mSupervisor).scheduleRestartTimeout(mActivity);
@@ -3340,14 +3385,20 @@
 
     }
 
-    private void setFoldablePosture(boolean isHalfFolded, boolean isTabletop) {
-        final DisplayRotation r = mActivity.mDisplayContent.getDisplayRotation();
+    private void setFoldablePosture(ActivityRecord activity, boolean isHalfFolded,
+            boolean isTabletop) {
+        final DisplayRotation r = activity.mDisplayContent.getDisplayRotation();
         doReturn(isHalfFolded).when(r).isDisplaySeparatingHinge();
         doReturn(false).when(r).isDeviceInPosture(any(DeviceState.class), anyBoolean());
         if (isHalfFolded) {
-            doReturn(true).when(r).isDeviceInPosture(DeviceState.HALF_FOLDED, isTabletop);
+            doReturn(true).when(r)
+                    .isDeviceInPosture(DeviceState.HALF_FOLDED, isTabletop);
         }
-        mActivity.recomputeConfiguration();
+        activity.recomputeConfiguration();
+    }
+
+    private void setFoldablePosture(boolean isHalfFolded, boolean isTabletop) {
+        setFoldablePosture(mActivity, isHalfFolded, isTabletop);
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceControlTests.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceControlTests.java
new file mode 100644
index 0000000..a27a5fd
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceControlTests.java
@@ -0,0 +1,116 @@
+/*
+ * 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.wm;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+import android.os.Parcel;
+import android.platform.test.annotations.Presubmit;
+import android.view.SurfaceControl;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Class for testing {@link SurfaceControl}.
+ *
+ * Build/Install/Run:
+ *  atest WmTests:SurfaceControlTests
+ */
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class SurfaceControlTests {
+
+    @SmallTest
+    @Test
+    public void testUseValidSurface() {
+        SurfaceControl sc = buildTestSurface();
+        SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+        t.setVisibility(sc, false);
+        sc.release();
+    }
+
+    @SmallTest
+    @Test
+    public void testUseInvalidSurface() {
+        SurfaceControl sc = buildTestSurface();
+        SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+        sc.release();
+        try {
+            t.setVisibility(sc, false);
+            fail("Expected exception from updating invalid surface");
+        } catch (Exception e) {
+            // Expected exception
+        }
+    }
+
+    @SmallTest
+    @Test
+    public void testUseInvalidSurface_debugEnabled() {
+        SurfaceControl sc = buildTestSurface();
+        SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+        try {
+            SurfaceControl.setDebugUsageAfterRelease(true);
+            sc.release();
+            try {
+                t.setVisibility(sc, false);
+                fail("Expected exception from updating invalid surface");
+            } catch (IllegalStateException ise) {
+                assertNotNull(ise.getCause());
+            } catch (Exception e) {
+                fail("Expected IllegalStateException with cause");
+            }
+        } finally {
+            SurfaceControl.setDebugUsageAfterRelease(false);
+        }
+    }
+
+    @SmallTest
+    @Test
+    public void testWriteInvalidSurface_debugEnabled() {
+        SurfaceControl sc = buildTestSurface();
+        SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+        Parcel p = Parcel.obtain();
+        try {
+            SurfaceControl.setDebugUsageAfterRelease(true);
+            sc.release();
+            try {
+                sc.writeToParcel(p, 0 /* flags */);
+                fail("Expected exception from writing invalid surface to parcel");
+            } catch (IllegalStateException ise) {
+                assertNotNull(ise.getCause());
+            } catch (Exception e) {
+                fail("Expected IllegalStateException with cause");
+            }
+        } finally {
+            SurfaceControl.setDebugUsageAfterRelease(false);
+            p.recycle();
+        }
+    }
+
+    private SurfaceControl buildTestSurface() {
+        return new SurfaceControl.Builder()
+                .setContainerLayer()
+                .setName("SurfaceControlTests")
+                .setCallsite("SurfaceControlTests")
+                .build();
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index 060f9d8..378e8be 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -511,7 +511,7 @@
     public void testApplyTransaction_enforceConfigurationChangeOnOrganizedTaskFragment() {
         // Throw exception if the transaction is trying to change a window that is not organized by
         // the organizer.
-        mTransaction.setBounds(mFragmentWindowToken, new Rect(0, 0, 100, 100));
+        mTransaction.setRelativeBounds(mFragmentWindowToken, new Rect(0, 0, 100, 100));
 
         assertApplyTransactionDisallowed(mTransaction);
 
@@ -1154,10 +1154,12 @@
         setupTaskFragmentInPip();
         spyOn(mWindowOrganizerController);
 
-        // Set bounds is ignored on a TaskFragment that is in a PIP Task.
-        mTransaction.setBounds(mFragmentWindowToken, new Rect(0, 0, 100, 100));
+        // Set relative bounds is ignored on a TaskFragment that is in a PIP Task.
+        mTransaction.setRelativeBounds(mFragmentWindowToken, new Rect(0, 0, 100, 100));
 
-        verify(mTaskFragment, never()).setBounds(any());
+        assertApplyTransactionAllowed(mTransaction);
+
+        verify(mTaskFragment, never()).setRelativeEmbeddedBounds(any());
     }
 
     @Test
@@ -1368,7 +1370,7 @@
     }
 
     /**
-     * For config change to untrusted embedded TaskFragment, we only allow bounds change within
+     * For config change to untrusted embedded TaskFragment, the bounds should be always within
      * its parent bounds.
      */
     @Test
@@ -1378,51 +1380,17 @@
         doReturn(false).when(mTaskFragment).isAllowedToBeEmbeddedInTrustedMode();
         final Task task = createTask(mDisplayContent);
         final Rect taskBounds = new Rect(task.getBounds());
-        final Rect taskAppBounds = new Rect(task.getWindowConfiguration().getAppBounds());
-        final int taskScreenWidthDp = task.getConfiguration().screenWidthDp;
-        final int taskScreenHeightDp = task.getConfiguration().screenHeightDp;
-        final int taskSmallestScreenWidthDp = task.getConfiguration().smallestScreenWidthDp;
         task.addChild(mTaskFragment, POSITION_TOP);
 
-        // Throw exception if the transaction is trying to change bounds of an untrusted outside of
-        // its parent's.
-
-        // setBounds
+        // When set a relative bounds outside of its parent's, it is allowed, but the actual
+        // TaskFragment bounds will be updated to be fit the parent's bounds.
         final Rect tfBounds = new Rect(taskBounds);
         tfBounds.right++;
-        mTransaction.setBounds(mFragmentWindowToken, tfBounds);
-        assertApplyTransactionDisallowed(mTransaction);
-
-        mTransaction.setBounds(mFragmentWindowToken, taskBounds);
+        mTransaction.setRelativeBounds(mFragmentWindowToken, tfBounds);
         assertApplyTransactionAllowed(mTransaction);
 
-        // setAppBounds
-        final Rect tfAppBounds = new Rect(taskAppBounds);
-        tfAppBounds.right++;
-        mTransaction.setAppBounds(mFragmentWindowToken, tfAppBounds);
-        assertApplyTransactionDisallowed(mTransaction);
-
-        mTransaction.setAppBounds(mFragmentWindowToken, taskAppBounds);
-        assertApplyTransactionAllowed(mTransaction);
-
-        // setScreenSizeDp
-        mTransaction.setScreenSizeDp(mFragmentWindowToken, taskScreenWidthDp + 1,
-                taskScreenHeightDp + 1);
-        assertApplyTransactionDisallowed(mTransaction);
-
-        mTransaction.setScreenSizeDp(mFragmentWindowToken, taskScreenWidthDp, taskScreenHeightDp);
-        assertApplyTransactionAllowed(mTransaction);
-
-        // setSmallestScreenWidthDp
-        mTransaction.setSmallestScreenWidthDp(mFragmentWindowToken, taskSmallestScreenWidthDp + 1);
-        assertApplyTransactionDisallowed(mTransaction);
-
-        mTransaction.setSmallestScreenWidthDp(mFragmentWindowToken, taskSmallestScreenWidthDp);
-        assertApplyTransactionAllowed(mTransaction);
-
-        // Any of the change mask is not allowed.
-        mTransaction.setFocusable(mFragmentWindowToken, false);
-        assertApplyTransactionDisallowed(mTransaction);
+        assertEquals(tfBounds, mTaskFragment.getRelativeEmbeddedBounds());
+        assertEquals(taskBounds, mTaskFragment.getBounds());
     }
 
     // TODO(b/232871351): add test for minimum dimension violation in startActivityInTaskFragment
@@ -1454,7 +1422,7 @@
     }
 
     @Test
-    public void testMinDimensionViolation_SetBounds() {
+    public void testMinDimensionViolation_setRelativeBounds() {
         final Task task = createTask(mDisplayContent);
         mTaskFragment = new TaskFragmentBuilder(mAtm)
                 .setParentTask(task)
@@ -1472,12 +1440,14 @@
 
         // Shrink the TaskFragment to mTaskFragBounds to make its bounds smaller than activity's
         // minimum dimensions.
-        mTransaction.setBounds(mTaskFragment.mRemoteToken.toWindowContainerToken(), mTaskFragBounds)
+        mTransaction.setRelativeBounds(mTaskFragment.mRemoteToken.toWindowContainerToken(),
+                        mTaskFragBounds)
                 .setErrorCallbackToken(mErrorToken);
         assertApplyTransactionAllowed(mTransaction);
 
-        assertWithMessage("setBounds must not be performed.")
-                .that(mTaskFragment.getBounds()).isEqualTo(task.getBounds());
+        // When the requested bounds do not satisfy the min dimension, it will be reset to empty.
+        assertWithMessage("setRelativeBounds must not be performed.")
+                .that(mTaskFragment.getRelativeEmbeddedBounds()).isEqualTo(new Rect());
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index 2984de9..4dd5de3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -500,6 +500,51 @@
                 virtualDisplay.getDisplay().getDisplayId());
     }
 
+    @Test
+    public void testSetInTouchModeOnAllDisplays_perDisplayFocusDisabled() {
+        testSetInTouchModeOnAllDisplays(/* perDisplayFocusEnabled= */ false);
+    }
+
+    @Test
+    public void testSetInTouchModeOnAllDisplays_perDisplayFocusEnabled() {
+        testSetInTouchModeOnAllDisplays(/* perDisplayFocusEnabled= */ true);
+    }
+
+    private void testSetInTouchModeOnAllDisplays(boolean perDisplayFocusEnabled) {
+        // Create a couple of extra displays.
+        // setInTouchModeOnAllDisplays should ignore the ownFocus setting.
+        final VirtualDisplay virtualDisplay = createVirtualDisplay(/* ownFocus= */ false);
+        final VirtualDisplay virtualDisplayOwnFocus = createVirtualDisplay(/* ownFocus= */ true);
+
+        // Enable or disable global touch mode (config_perDisplayFocusEnabled setting).
+        // setInTouchModeOnAllDisplays should ignore this value.
+        Resources mockResources = mock(Resources.class);
+        spyOn(mContext);
+        when(mContext.getResources()).thenReturn(mockResources);
+        doReturn(perDisplayFocusEnabled).when(mockResources).getBoolean(
+                com.android.internal.R.bool.config_perDisplayFocusEnabled);
+
+        int callingPid = Binder.getCallingPid();
+        int callingUid = Binder.getCallingUid();
+        doReturn(false).when(mWm).checkCallingPermission(anyString(), anyString(), anyBoolean());
+        when(mWm.mAtmService.instrumentationSourceHasPermission(callingPid,
+                android.Manifest.permission.MODIFY_TOUCH_MODE_STATE)).thenReturn(true);
+
+        for (boolean inTouchMode : new boolean[]{true, false}) {
+            mWm.setInTouchModeOnAllDisplays(inTouchMode);
+            for (int i = 0; i < mWm.mRoot.mChildren.size(); ++i) {
+                DisplayContent dc = mWm.mRoot.mChildren.get(i);
+                // All displays that are not already in the desired touch mode are requested to
+                // change their touch mode.
+                if (dc.isInTouchMode() != inTouchMode) {
+                    verify(mWm.mInputManager).setInTouchMode(
+                            true, callingPid, callingUid, /* hasPermission= */ true,
+                            dc.getDisplay().getDisplayId());
+                }
+            }
+        }
+    }
+
     private VirtualDisplay createVirtualDisplay(boolean ownFocus) {
         // Create virtual display
         Point surfaceSize = new Point(
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index d13fb1b..b2d8fed 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -45,7 +45,6 @@
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
-import static android.view.WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -977,19 +976,6 @@
         assertFalse(sameTokenWindow.needsRelativeLayeringToIme());
     }
 
-    @UseTestDisplay(addWindows = {W_ACTIVITY, W_INPUT_METHOD})
-    @Test
-    public void testNeedsRelativeLayeringToIme_systemDialog() {
-        WindowState systemDialogWindow = createWindow(null, TYPE_SECURE_SYSTEM_OVERLAY,
-                mDisplayContent,
-                "SystemDialog", true);
-        mDisplayContent.setImeLayeringTarget(mAppWindow);
-        mAppWindow.getRootTask().setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
-        makeWindowVisible(mImeWindow);
-        systemDialogWindow.mAttrs.flags |= FLAG_ALT_FOCUSABLE_IM;
-        assertTrue(systemDialogWindow.needsRelativeLayeringToIme());
-    }
-
     @Test
     public void testSetFreezeInsetsState() {
         final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
diff --git a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
index 9cde7e3..6cf2b2d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
@@ -22,7 +22,6 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
@@ -32,7 +31,6 @@
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
-import static android.view.WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL;
 import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL;
 import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
@@ -552,30 +550,6 @@
     }
 
     @Test
-    public void testSystemDialogWindow_expectHigherThanIme_inMultiWindow() {
-        // Simulate the app window is in multi windowing mode and being IME target
-        mAppWindow.getConfiguration().windowConfiguration.setWindowingMode(
-                WINDOWING_MODE_MULTI_WINDOW);
-        mDisplayContent.setImeLayeringTarget(mAppWindow);
-        mDisplayContent.setImeInputTarget(mAppWindow);
-        makeWindowVisible(mImeWindow);
-
-        // Create a popupWindow
-        final WindowState systemDialogWindow = createWindow(null, TYPE_SECURE_SYSTEM_OVERLAY,
-                mDisplayContent, "SystemDialog", true);
-        systemDialogWindow.mAttrs.flags |= FLAG_ALT_FOCUSABLE_IM;
-        spyOn(systemDialogWindow);
-
-        mDisplayContent.assignChildLayers(mTransaction);
-
-        // Verify the surface layer of the popupWindow should higher than IME
-        verify(systemDialogWindow).needsRelativeLayeringToIme();
-        assertThat(systemDialogWindow.needsRelativeLayeringToIme()).isTrue();
-        assertZOrderGreaterThan(mTransaction, systemDialogWindow.getSurfaceControl(),
-                mDisplayContent.getImeContainer().getSurfaceControl());
-    }
-
-    @Test
     public void testImeScreenshotLayer() {
         final Task task = createTask(mDisplayContent);
         final WindowState imeAppTarget = createAppWindow(task, TYPE_APPLICATION, "imeAppTarget");
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index 12e5ed9..77b2638 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -1627,6 +1627,8 @@
                     int status, int mRequest, long mFunctions, boolean mChargingFunctions);
 
         public abstract void getUsbSpeedCb(int speed);
+
+        public abstract void resetCb(int status);
     }
 
     private static final class UsbHandlerLegacy extends UsbHandler {
@@ -1988,14 +1990,30 @@
             return true;
         }
 
+        /**
+         * This callback function is only applicable for USB Gadget HAL,
+         * USBHandlerLegacy does not supported it.
+         */
         @Override
         public void setCurrentUsbFunctionsCb(long functions,
                     int status, int mRequest, long mFunctions, boolean mChargingFunctions){
         }
 
+        /**
+         * This callback function is only applicable for USB Gadget HAL,
+         * USBHandlerLegacy does not supported it.
+         */
         @Override
         public void getUsbSpeedCb(int speed){
         }
+
+        /**
+         * This callback function is only applicable for USB Gadget HAL,
+         * USBHandlerLegacy does not supported it.
+         */
+        @Override
+        public void resetCb(int status){
+        }
     }
 
     private static final class UsbHandlerHal extends UsbHandler {
@@ -2147,6 +2165,7 @@
                     }
                     break;
                 case MSG_RESET_USB_GADGET:
+                    operationId = sUsbOperationCount.incrementAndGet();
                     synchronized (mGadgetProxyLock) {
                         if (mUsbGadgetHal == null) {
                             Slog.e(TAG, "reset Usb Gadget mUsbGadgetHal is null");
@@ -2160,7 +2179,7 @@
                             if (mConfigured) {
                                 mResetUsbGadgetDisableDebounce = true;
                             }
-                            mUsbGadgetHal.reset();
+                            mUsbGadgetHal.reset(operationId);
                         } catch (Exception e) {
                             Slog.e(TAG, "reset Usb Gadget failed", e);
                             mResetUsbGadgetDisableDebounce = false;
@@ -2222,6 +2241,12 @@
             mUsbSpeed = speed;
         }
 
+        @Override
+        public void resetCb(int status) {
+            if (status != Status.SUCCESS)
+                Slog.e(TAG, "resetCb fail");
+        }
+
         private void setUsbConfig(long config, boolean chargingFunctions, int operationId) {
             if (true) Slog.d(TAG, "setUsbConfig(" + config + ") request:" + ++mCurrentRequest);
             /**
@@ -2363,6 +2388,10 @@
         mHandler.getUsbSpeedCb(speed);
     }
 
+    public void resetCb(int status) {
+        mHandler.resetCb(status);
+    }
+
     /**
      * Returns a dup of the control file descriptor for the given function.
      */
diff --git a/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetAidl.java b/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetAidl.java
index bdfe60a..d47ccc7 100644
--- a/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetAidl.java
+++ b/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetAidl.java
@@ -23,6 +23,7 @@
 import android.annotation.Nullable;
 import android.hardware.usb.gadget.IUsbGadget;
 import android.hardware.usb.gadget.IUsbGadgetCallback;
+import android.hardware.usb.Status;
 import android.hardware.usb.UsbManager.UsbGadgetHalVersion;
 import android.os.ServiceManager;
 import android.os.IBinder;
@@ -139,14 +140,15 @@
     }
 
     @Override
-    public void reset() {
+    public void reset(long operationId) {
         try {
             synchronized (mGadgetProxyLock) {
-                mGadgetProxy.reset();
+                mGadgetProxy.reset(new UsbGadgetCallback(), operationId);
             }
         } catch (RemoteException e) {
             logAndPrintException(mPw,
-                    "RemoteException while calling getUsbSpeed", e);
+                    "RemoteException while calling getUsbSpeed"
+                    + ", opID:" + operationId, e);
             return;
         }
     }
@@ -155,7 +157,7 @@
     public void setCurrentUsbFunctions(int mRequest, long mFunctions,
             boolean mChargingFunctions, int timeout, long operationId) {
         try {
-            mUsbGadgetCallback = new UsbGadgetCallback(mRequest,
+            mUsbGadgetCallback = new UsbGadgetCallback(null, mRequest,
                                       mFunctions, mChargingFunctions);
             synchronized (mGadgetProxyLock) {
                 mGadgetProxy.setCurrentUsbFunctions(mFunctions, mUsbGadgetCallback,
@@ -174,6 +176,7 @@
     }
 
     private class UsbGadgetCallback extends IUsbGadgetCallback.Stub {
+        public IndentingPrintWriter mPw;
         public int mRequest;
         public long mFunctions;
         public boolean mChargingFunctions;
@@ -181,8 +184,9 @@
         UsbGadgetCallback() {
         }
 
-        UsbGadgetCallback(int request, long functions,
+        UsbGadgetCallback(IndentingPrintWriter pw, int request, long functions,
                 boolean chargingFunctions) {
+            mPw = pw;
             mRequest = request;
             mFunctions = functions;
             mChargingFunctions = chargingFunctions;
@@ -191,6 +195,15 @@
         @Override
         public void setCurrentUsbFunctionsCb(long functions,
                 int status, long transactionId) {
+            if (status == Status.SUCCESS) {
+                logAndPrint(Log.INFO, mPw, "Usb setCurrentUsbFunctionsCb"
+                + " ,functions:" + functions + " ,status:" + status
+                + " ,transactionId:" + transactionId);
+            } else {
+                logAndPrint(Log.ERROR, mPw, "Usb setCurrentUsbFunctionsCb failed"
+                + " ,functions:" + functions + " ,status:" + status
+                + " ,transactionId:" + transactionId);
+            }
             mDeviceManager.setCurrentUsbFunctionsCb(functions, status,
                     mRequest, mFunctions, mChargingFunctions);
         }
@@ -198,15 +211,38 @@
         @Override
         public void getCurrentUsbFunctionsCb(long functions,
                 int status, long transactionId) {
+            if (status == Status.SUCCESS) {
+                logAndPrint(Log.INFO, mPw, "Usb getCurrentUsbFunctionsCb"
+                + " ,functions:" + functions + " ,status:" + status
+                + " ,transactionId:" + transactionId);
+            } else {
+                logAndPrint(Log.ERROR, mPw, "Usb getCurrentUsbFunctionsCb failed"
+                + " ,functions:" + functions + " ,status:" + status
+                + " ,transactionId:" + transactionId);
+            }
             mDeviceManager.getCurrentUsbFunctionsCb(functions, status);
         }
 
         @Override
         public void getUsbSpeedCb(int speed, long transactionId) {
+            logAndPrint(Log.INFO, mPw, "getUsbSpeedCb speed:"
+                + speed + " ,transactionId:" + transactionId);
             mDeviceManager.getUsbSpeedCb(speed);
         }
 
         @Override
+        public void resetCb(int status, long transactionId) {
+            if (status == Status.SUCCESS) {
+                logAndPrint(Log.INFO, mPw, "Usb resetCb status:"
+                + status + " ,transactionId:" + transactionId);
+            } else {
+                logAndPrint(Log.ERROR, mPw, "Usb resetCb status"
+                + status + " ,transactionId:" + transactionId);
+            }
+            mDeviceManager.resetCb(status);
+        }
+
+        @Override
         public String getInterfaceHash() {
             return IUsbGadgetCallback.HASH;
         }
diff --git a/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHal.java b/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHal.java
index 267247b..7b52f46 100644
--- a/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHal.java
+++ b/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHal.java
@@ -112,8 +112,11 @@
      * This function is used to reset USB gadget driver.
      * Performs USB data connection reset. The connection will disconnect and
      * reconnect.
+     *
+     * @param transactionId Used for tracking the current request and is passed down to the HAL
+     *                      implementation as needed.
      */
-    public void reset();
+    public void reset(long transactionId);
 
     /**
      * Invoked to query the version of current gadget hal implementation.
diff --git a/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHidl.java b/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHidl.java
index 3e5ecc5..2c5fcb8 100644
--- a/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHidl.java
+++ b/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHidl.java
@@ -188,7 +188,7 @@
     }
 
     @Override
-    public void reset() {
+    public void reset(long transactionId) {
         try {
             synchronized(mGadgetProxyLock) {
                 if (android.hardware.usb.gadget.V1_2.IUsbGadget.castFrom(mGadgetProxy) != null) {
@@ -199,7 +199,7 @@
             }
         } catch (RemoteException e) {
             logAndPrintException(mPw,
-                    "RemoteException while calling getUsbSpeed", e);
+                    "RemoteException while calling reset", e);
             return;
         }
     }
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 0abed0b..717f4e7 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -230,7 +230,7 @@
     class LocalService extends VoiceInteractionManagerInternal {
         @Override
         public void startLocalVoiceInteraction(@NonNull IBinder callingActivity,
-                @Nullable String attributionTag, @NonNull Bundle options) {
+                @Nullable String attributionTag, @Nullable Bundle options) {
             if (DEBUG) {
                 Slog.i(TAG, "startLocalVoiceInteraction " + callingActivity);
             }
@@ -426,7 +426,7 @@
 
         // TODO: VI Make sure the caller is the current user or profile
         void startLocalVoiceInteraction(@NonNull final IBinder token,
-                @Nullable String attributionTag, @NonNull Bundle options) {
+                @Nullable String attributionTag, @Nullable Bundle options) {
             if (mImpl == null) return;
 
             final int callingUid = Binder.getCallingUid();
@@ -945,7 +945,7 @@
         }
 
         @Override
-        public void showSession(@NonNull Bundle args, int flags, @Nullable String attributionTag) {
+        public void showSession(@Nullable Bundle args, int flags, @Nullable String attributionTag) {
             synchronized (this) {
                 enforceIsCurrentVoiceInteractionService();
 
@@ -976,7 +976,7 @@
         }
 
         @Override
-        public boolean showSessionFromSession(@NonNull IBinder token, @NonNull Bundle sessionArgs,
+        public boolean showSessionFromSession(@NonNull IBinder token, @Nullable Bundle sessionArgs,
                 int flags, @Nullable String attributionTag) {
             synchronized (this) {
                 if (mImpl == null) {
@@ -1828,7 +1828,7 @@
 
         @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_VOICE_INTERACTION_SERVICE)
         @Override
-        public boolean showSessionForActiveService(@NonNull Bundle args, int sourceFlags,
+        public boolean showSessionForActiveService(@Nullable Bundle args, int sourceFlags,
                 @Nullable String attributionTag,
                 @Nullable IVoiceInteractionSessionShowCallback showCallback,
                 @Nullable IBinder activityToken) {
diff --git a/telephony/java/android/telephony/AnomalyReporter.java b/telephony/java/android/telephony/AnomalyReporter.java
index 061b71b..d676eee 100644
--- a/telephony/java/android/telephony/AnomalyReporter.java
+++ b/telephony/java/android/telephony/AnomalyReporter.java
@@ -110,8 +110,15 @@
             return;
         }
 
-        // Don't report if the server-side flag isn't loaded, as it implies other anomaly report
-        // related config hasn't loaded.
+        //always write atoms to statsd
+        TelephonyStatsLog.write(
+                TELEPHONY_ANOMALY_DETECTED,
+                carrierId,
+                eventId.getLeastSignificantBits(),
+                eventId.getMostSignificantBits());
+
+        // Don't report via Intent if the server-side flag isn't loaded, as it implies other anomaly
+        // report related config hasn't loaded.
         try {
             boolean isAnomalyReportEnabledFromServer = DeviceConfig.getBoolean(
                     DeviceConfig.NAMESPACE_TELEPHONY, KEY_IS_TELEPHONY_ANOMALY_REPORT_ENABLED,
@@ -122,12 +129,6 @@
             return;
         }
 
-        TelephonyStatsLog.write(
-                TELEPHONY_ANOMALY_DETECTED,
-                carrierId,
-                eventId.getLeastSignificantBits(),
-                eventId.getMostSignificantBits());
-
         // If this event has already occurred, skip sending intents for it; regardless log its
         // invocation here.
         Integer count = sEvents.containsKey(eventId) ? sEvents.get(eventId) + 1 : 1;
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 5e15412..9e2e092 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -6990,11 +6990,11 @@
 
         /**
          * Maximum Retry Count for SMS over IMS on Failure, If the Retry Count exceeds this value,
-         * and if the retry count is less than KEY_SMS_MAX_RETRY_COUNT_INT
+         * and if the retry count is less than {@link #KEY_SMS_MAX_RETRY_COUNT_INT}
          * sending SMS should fallback to CS
          */
-        public static final String KEY_SMS_MAX_RETRY_COUNT_OVER_IMS_INT =
-                KEY_PREFIX + "sms_max_retry_count_over_ims_int";
+        public static final String KEY_SMS_MAX_RETRY_OVER_IMS_COUNT_INT =
+                KEY_PREFIX + "sms_max_retry_over_ims_count_int";
 
         /**
          * Delay Timer Value in milliseconds
@@ -7062,7 +7062,7 @@
             defaults.putInt(KEY_SMS_OVER_IMS_FORMAT_INT, SMS_FORMAT_3GPP);
 
             defaults.putInt(KEY_SMS_MAX_RETRY_COUNT_INT, 3);
-            defaults.putInt(KEY_SMS_MAX_RETRY_COUNT_OVER_IMS_INT, 3);
+            defaults.putInt(KEY_SMS_MAX_RETRY_OVER_IMS_COUNT_INT, 3);
             defaults.putInt(KEY_SMS_OVER_IMS_SEND_RETRY_DELAY_MILLIS_INT,
                     2000);
             defaults.putInt(KEY_SMS_TR1_TIMER_MILLIS_INT, 130000);
@@ -10694,4 +10694,38 @@
         }
         trm.removeCarrierConfigChangedListener(listener);
     }
+
+    /**
+     * Get subset of specified carrier configuration if available or empty bundle, without throwing
+     * {@link RuntimeException} to caller.
+     *
+     * <p>This is a system internally used only utility to reduce the repetitive logic.
+     *
+     * <p>Requires Permission:
+     * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}, or the calling app
+     * has carrier privileges on the specified subscription (see
+     * {@link TelephonyManager#hasCarrierPrivileges()}).
+     *
+     * @param context Context used to get the CarrierConfigManager service.
+     * @param subId The subscription ID to get the config from.
+     * @param keys The config keys the client is interested in.
+     * @return Config bundle with key/value for the specified keys or empty bundle when failed
+     * @hide
+     */
+    @RequiresPermission(anyOf = {
+            Manifest.permission.READ_PHONE_STATE,
+            "carrier privileges",
+    })
+    @NonNull
+    public static PersistableBundle getCarrierConfigSubset(
+            @NonNull Context context, int subId, @NonNull String... keys) {
+        PersistableBundle configs = null;
+        CarrierConfigManager ccm = context.getSystemService(CarrierConfigManager.class);
+        try {
+            configs = ccm.getConfigForSubId(subId, keys);
+        } catch (RuntimeException exception) {
+            Rlog.w(TAG, "CarrierConfigLoader is not available.");
+        }
+        return configs != null ? configs : new PersistableBundle();
+    }
 }
diff --git a/telephony/java/android/telephony/DataFailCause.java b/telephony/java/android/telephony/DataFailCause.java
index b83b400..c4d760f 100644
--- a/telephony/java/android/telephony/DataFailCause.java
+++ b/telephony/java/android/telephony/DataFailCause.java
@@ -1012,6 +1012,8 @@
     public static final int IWLAN_DNS_RESOLUTION_NAME_FAILURE = 0x4004;
     /** No response received from the DNS Server due to a timeout*/
     public static final int IWLAN_DNS_RESOLUTION_TIMEOUT = 0x4005;
+    /** Expected to update or bring down an ePDG tunnel, but no tunnel found*/
+    public static final int IWLAN_TUNNEL_NOT_FOUND = 0x4006;
 
     // OEM sepecific error codes. To be used by OEMs when they don't
     // want to reveal error code which would be replaced by ERROR_UNSPECIFIED
@@ -1505,6 +1507,7 @@
         sFailCauseMap.put(IWLAN_IKEV2_CERT_INVALID, "IWLAN_IKEV2_CERT_INVALID");
         sFailCauseMap.put(IWLAN_DNS_RESOLUTION_NAME_FAILURE, "IWLAN_DNS_RESOLUTION_NAME_FAILURE");
         sFailCauseMap.put(IWLAN_DNS_RESOLUTION_TIMEOUT, "IWLAN_DNS_RESOLUTION_TIMEOUT");
+        sFailCauseMap.put(IWLAN_TUNNEL_NOT_FOUND, "IWLAN_TUNNEL_NOT_FOUND");
         sFailCauseMap.put(OEM_DCFAILCAUSE_1, "OEM_DCFAILCAUSE_1");
         sFailCauseMap.put(OEM_DCFAILCAUSE_2, "OEM_DCFAILCAUSE_2");
         sFailCauseMap.put(OEM_DCFAILCAUSE_3, "OEM_DCFAILCAUSE_3");
diff --git a/telephony/java/android/telephony/satellite/ISatellitePositionUpdateCallback.aidl b/telephony/java/android/telephony/satellite/ISatelliteCapabilitiesConsumer.aidl
similarity index 65%
copy from telephony/java/android/telephony/satellite/ISatellitePositionUpdateCallback.aidl
copy to telephony/java/android/telephony/satellite/ISatelliteCapabilitiesConsumer.aidl
index 5c3fa32..f3ae245 100644
--- a/telephony/java/android/telephony/satellite/ISatellitePositionUpdateCallback.aidl
+++ b/telephony/java/android/telephony/satellite/ISatelliteCapabilitiesConsumer.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright 2023 The Android Open Source Project
+ * 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.
@@ -16,13 +16,12 @@
 
 package android.telephony.satellite;
 
-import android.telephony.satellite.PointingInfo;
+import android.telephony.satellite.SatelliteCapabilities;
 
 /**
- * Callback for position updates from the satellite service.
+ * Consumer for a SatelliteCapabilities result from the satellite service.
  * @hide
  */
-oneway interface ISatellitePositionUpdateCallback {
-    void onSatellitePositionUpdate(in PointingInfo pointingInfo);
-    void onMessageTransferStateUpdate(in int state);
+oneway interface ISatelliteCapabilitiesConsumer {
+    void accept(in SatelliteCapabilities result);
 }
diff --git a/telephony/java/android/telephony/satellite/ISatellitePositionUpdateCallback.aidl b/telephony/java/android/telephony/satellite/ISatelliteStateListener.aidl
similarity index 82%
rename from telephony/java/android/telephony/satellite/ISatellitePositionUpdateCallback.aidl
rename to telephony/java/android/telephony/satellite/ISatelliteStateListener.aidl
index 5c3fa32..6fb0979 100644
--- a/telephony/java/android/telephony/satellite/ISatellitePositionUpdateCallback.aidl
+++ b/telephony/java/android/telephony/satellite/ISatelliteStateListener.aidl
@@ -19,10 +19,11 @@
 import android.telephony.satellite.PointingInfo;
 
 /**
- * Callback for position updates from the satellite service.
+ * Interface for satellite state listener.
  * @hide
  */
-oneway interface ISatellitePositionUpdateCallback {
+oneway interface ISatelliteStateListener {
+    void onSatelliteProvisionStateChanged(in int[] features, in boolean provisioned);
     void onSatellitePositionUpdate(in PointingInfo pointingInfo);
     void onMessageTransferStateUpdate(in int state);
 }
diff --git a/telephony/java/android/telephony/satellite/SatelliteCallback.java b/telephony/java/android/telephony/satellite/SatelliteCallback.java
new file mode 100644
index 0000000..1b82d06
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/SatelliteCallback.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.satellite;
+
+import android.annotation.NonNull;
+import android.os.Binder;
+import android.telephony.satellite.stub.SatelliteImplBase;
+
+import java.lang.ref.WeakReference;
+import java.util.concurrent.Executor;
+
+/**
+ * A callback class for monitoring changes in specific satellite service states on the device,
+ * including provision state, position update, message transfer state and others.
+ * <p>
+ * To register a callback, use a {@link SatelliteCallback} which implements the interested
+ * interfaces. For example,
+ * FakeSatelliteProvisionStateCallback extends {@link SatelliteCallback} implements
+ * {@link SatelliteCallback.SatelliteProvisionStateListener}.
+ * <p>
+ * Then override the methods for the state that you wish to receive updates for, and
+ * pass your SatelliteCallback object to the corresponding register function like
+ * {@link SatelliteManager#registerForSatelliteProvisionStateChanged}.
+ * <p>
+ *
+ * @hide
+ */
+public class SatelliteCallback {
+    private ISatelliteStateListener mCallbackStub;
+
+    /**
+     * The SatelliteCallback needs an executor to execute the callback interfaces.
+     */
+    public void init(@NonNull Executor executor) {
+        if (executor == null) {
+            throw new IllegalArgumentException("SatelliteCallback executor must be non-null");
+        }
+        mCallbackStub = new ISatelliteStateListenerStub(this, executor);
+    }
+
+    public ISatelliteStateListener getCallbackStub() {
+        return mCallbackStub;
+    }
+
+    /**
+     * Interface for satellite provision state change listener.
+     */
+    public interface SatelliteProvisionStateListener {
+        /**
+         * Called when satellite provision state changes.
+         *
+         * @param features The list of provisioned features.
+         * @param provisioned The new provision state. {@code true} means satellite is provisioned
+         *                    {@code false} means satellite is not provisioned.
+         */
+        void onSatelliteProvisionStateChanged(
+                @SatelliteImplBase.Feature int[] features, boolean provisioned);
+    }
+
+    /**
+     * Interface for position update change listener.
+     */
+    public interface SatellitePositionUpdateListener {
+        /**
+         * Called when the satellite position changes.
+         *
+         * @param pointingInfo The pointing info containing the satellite location.
+         */
+        void onSatellitePositionUpdate(@NonNull PointingInfo pointingInfo);
+
+        /**
+         * Called when satellite message transfer state changes.
+         *
+         * @param state The new message transfer state.
+         */
+        void onMessageTransferStateUpdate(
+                @SatelliteManager.SatelliteMessageTransferState int state);
+    }
+
+    private static class ISatelliteStateListenerStub extends ISatelliteStateListener.Stub {
+        private WeakReference<SatelliteCallback> mSatelliteCallbackWeakRef;
+        private Executor mExecutor;
+
+        ISatelliteStateListenerStub(SatelliteCallback satelliteCallback, Executor executor) {
+            mSatelliteCallbackWeakRef = new WeakReference<>(satelliteCallback);
+            mExecutor = executor;
+        }
+
+        public void onSatelliteProvisionStateChanged(
+                @SatelliteImplBase.Feature int[] features, boolean provisioned) {
+            SatelliteProvisionStateListener listener =
+                    (SatelliteProvisionStateListener) mSatelliteCallbackWeakRef.get();
+            if (listener == null) return;
+
+            Binder.withCleanCallingIdentity(() -> mExecutor.execute(
+                    () -> listener.onSatelliteProvisionStateChanged(features, provisioned)));
+        }
+
+        public void onSatellitePositionUpdate(@NonNull PointingInfo pointingInfo) {
+            SatellitePositionUpdateListener listener =
+                    (SatellitePositionUpdateListener) mSatelliteCallbackWeakRef.get();
+            if (listener == null) return;
+
+            Binder.withCleanCallingIdentity(() -> mExecutor.execute(
+                    () -> listener.onSatellitePositionUpdate(pointingInfo)));
+        }
+
+        public void onMessageTransferStateUpdate(
+                @SatelliteManager.SatelliteMessageTransferState int state) {
+            SatellitePositionUpdateListener listener =
+                    (SatellitePositionUpdateListener) mSatelliteCallbackWeakRef.get();
+            if (listener == null) return;
+
+            Binder.withCleanCallingIdentity(() -> mExecutor.execute(
+                    () -> listener.onMessageTransferStateUpdate(state)));
+        }
+    }
+}
diff --git a/telephony/java/android/telephony/satellite/SatelliteCapabilities.aidl b/telephony/java/android/telephony/satellite/SatelliteCapabilities.aidl
new file mode 100644
index 0000000..a09306b
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/SatelliteCapabilities.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2023, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.telephony.satellite;
+
+parcelable SatelliteCapabilities;
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 7a60c58..87bfcba 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -26,26 +26,30 @@
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.os.Binder;
+import android.os.CancellationSignal;
+import android.os.ICancellationSignal;
 import android.os.RemoteException;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyFrameworkInitializer;
-import android.util.ArrayMap;
+import android.telephony.satellite.stub.SatelliteImplBase;
 
+import com.android.internal.telephony.IBooleanConsumer;
+import com.android.internal.telephony.IIntArrayConsumer;
 import com.android.internal.telephony.IIntegerConsumer;
 import com.android.internal.telephony.ITelephony;
 import com.android.telephony.Rlog;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.util.Map;
 import java.util.Objects;
 import java.util.concurrent.Executor;
-import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Consumer;
 
 /**
  * Manages satellite operations such as provisioning, pointing, messaging, location sharing, etc.
- * To get the object, call {@link Context#getSystemService(Context.SATELLITE_SERVICE)}.
+ * To get the object, call {@link Context#getSystemService(String)}. This object is associated
+ * with the {@link SubscriptionManager#DEFAULT_SUBSCRIPTION_ID} and the satellite service is
+ * associated with the device rather than a subscription.
  * To create an instance of {@link SatelliteManager} associated with a specific subscription ID,
  * call {@link #createForSubscriptionId(int)}.
  *
@@ -56,18 +60,11 @@
     private static final String TAG = "SatelliteManager";
 
     /**
-     * Map of all SatellitePositionUpdateCallback and their associated callback ids.
-     */
-    private final Map<SatellitePositionUpdateCallback, Integer> mSatellitePositionUpdateCallbacks =
-            new ArrayMap<>();
-
-    /**
-     * AtomicInteger for the id of the next SatellitePositionUpdateCallback.
-     */
-    private final AtomicInteger mSatellitePositionUpdateCallbackId = new AtomicInteger(0);
-
-    /**
-     * The subscription ID for this SatelliteManager.
+     * The subscription ID for this SatelliteManager. If the
+     * {@link SubscriptionManager#DEFAULT_SUBSCRIPTION_ID} is used, the satellite service will be
+     * associated with the device rather than a subscription. If an active subscription ID
+     * {@link SubscriptionManager#isActiveSubId(int)} is provided, the satellite service will be
+     * associated with that active subscription.
      */
     private final int mSubId;
 
@@ -82,7 +79,6 @@
      * @param context The context the SatelliteManager belongs to.
      */
     public SatelliteManager(@Nullable Context context) {
-        // TODO: replace DEFAULT_SUBSCRIPTION_ID with DEFAULT_SATELLITE_SUBSCRIPTION_ID
         this(context, SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
     }
 
@@ -120,23 +116,24 @@
      */
     public static final int SATELLITE_SERVICE_SERVER_ERROR = 2;
     /**
-     * Unexpected telephony internal error.
+     * Telephony is not in a valid state to serve requests from clients.
      */
-    public static final int SATELLITE_SERVICE_TELEPHONY_INTERNAL_ERROR = 3;
+    public static final int SATELLITE_SERVICE_INVALID_TELEPHONY_STATE = 3;
     /**
-     * Modem error received from the satellite service.
+     * RIL layer got an unexpected or incorrect response from modem for a request.
      */
-    public static final int SATELLITE_SERVICE_MODEM_ERROR = 4;
+    public static final int SATELLITE_SERVICE_UNEXPECTED_MODEM_RESPONSE = 4;
     /**
-     * System error received from the satellite service.
+     * An error in the RIL layer occurs when processing a request. This generic RIL error should be
+     * used only when other RIL specific errors cannot be used.
      */
-    public static final int SATELLITE_SERVICE_SYSTEM_ERROR = 5;
+    public static final int SATELLITE_SERVICE_RIL_ERROR = 5;
     /**
-     * Invalid arguments passed.
+     * RIL layer has received a request with invalid arguments from the Telephony framework.
      */
     public static final int SATELLITE_SERVICE_INVALID_ARGUMENTS = 6;
     /**
-     * Invalid modem state.
+     * Satellite modem is not in a valid state to serve requests from clients.
      */
     public static final int SATELLITE_SERVICE_INVALID_MODEM_STATE = 7;
     /**
@@ -144,72 +141,363 @@
      */
     public static final int SATELLITE_SERVICE_INVALID_SIM_STATE = 8;
     /**
-     * Invalid state.
+     * RIL layer is not in a valid state to serve requests from clients.
      */
-    public static final int SATELLITE_SERVICE_INVALID_STATE = 9;
+    public static final int SATELLITE_SERVICE_INVALID_RIL_STATE = 9;
     /**
-     * Satellite service is unavailable.
+     * Radio did not start or is resetting.
      */
-    public static final int SATELLITE_SERVICE_NOT_AVAILABLE = 10;
+    public static final int SATELLITE_SERVICE_RADIO_NOT_AVAILABLE = 10;
     /**
-     * Satellite service is not supported by the device or OS.
+     * The request is not supported by either the satellite modem or the network.
      */
-    public static final int SATELLITE_SERVICE_NOT_SUPPORTED = 11;
+    public static final int SATELLITE_SERVICE_REQUEST_NOT_SUPPORTED = 11;
     /**
-     * Satellite service is rate limited.
+     * Requests denied by the satellite modem or the network due to overly-frequent requests.
      */
-    public static final int SATELLITE_SERVICE_RATE_LIMITED = 12;
+    public static final int SATELLITE_SERVICE_REQUEST_RATE_LIMITED = 12;
     /**
-     * Satellite service has no memory available.
+     * Satellite modem or network has no resources available to handle requests from clients.
      */
-    public static final int SATELLITE_SERVICE_NO_MEMORY = 13;
+    public static final int SATELLITE_SERVICE_NO_RESOURCES = 13;
     /**
-     * Satellite service has no resources available.
+     * Telephony framework failed to send a request to the satellite service on the device or the
+     * satellite modem.
      */
-    public static final int SATELLITE_SERVICE_NO_RESOURCES = 14;
+    public static final int SATELLITE_SERVICE_REQUEST_FAILED = 14;
     /**
-     * Failed to send a request to the satellite service.
+     * A generic error which should be used only when other specific errors cannot be used.
      */
-    public static final int SATELLITE_SERVICE_REQUEST_FAILED = 15;
+    public static final int SATELLITE_SERVICE_ERROR = 15;
     /**
-     * Failed to send a request to the satellite service for the given subscription ID.
+     * Satellite service is disabled on the requested subscription or the device.
      */
-    public static final int SATELLITE_SERVICE_INVALID_SUBSCRIPTION_ID = 16;
+    public static final int SATELLITE_SERVICE_DISABLED = 16;
     /**
-     * Error received from satellite service.
+     * Satellite is already provisioned for the subscription or the device.
      */
-    public static final int SATELLITE_SERVICE_ERROR = 17;
+    public static final int SATELLITE_SERVICE_ALREADY_PROVISIONED = 17;
     /**
-     * Satellite service is disabled on the requested subscription.
+     * Provisioning is already in progress.
      */
-    public static final int SATELLITE_SERVICE_DISABLED = 18;
+    public static final int SATELLITE_SERVICE_PROVISION_IN_PROGRESS = 18;
+    /**
+     * The ongoing request was aborted by either the satellite modem or the network.
+     */
+    public static final int SATELLITE_SERVICE_REQUEST_ABORTED = 19;
+    /**
+     * The device/subscription is barred to access the satellite service.
+     */
+    public static final int SATELLITE_SERVICE_ACCESS_BARRED = 20;
+    /**
+     * The requesting feature is not supported by the satellite service provider.
+     */
+    public static final int SATELLITE_SERVICE_FEATURE_NOT_SUPPORTED = 21;
+    /**
+     * The modem of the device is not compatible with the satellite service provider.
+     */
+    public static final int SATELLITE_SERVICE_MODEM_INCOMPATIBLE = 22;
+    /**
+     * The satellite network is not ready to serve requests from clients.
+     */
+    public static final int SATELLITE_SERVICE_NETWORK_NOT_READY = 23;
+    /**
+     * The satellite server is not ready to serve requests from clients.
+     */
+    public static final int SATELLITE_SERVICE_SERVER_NOT_READY = 24;
+    /**
+     * The request was rejected by the satellite server.
+     */
+    public static final int SATELLITE_SERVICE_SERVER_REJECT = 25;
+    /**
+     * Satellite modem timeout to receive ACK or response from the satellite network after
+     * sending a request to the network.
+     */
+    public static final int SATELLITE_SERVICE_NETWORK_TIMEOUT = 26;
+    /**
+     * Satellite modem cannot detect any satellite signal.
+     */
+    public static final int SATELLITE_SERVICE_NO_SATELLITE_SIGNAL = 27;
+    /**
+     * Device does not have a subscription.
+     */
+    public static final int SATELLITE_SERVICE_NO_SUBSCRIPTION = 28;
+    /**
+     * Operation is not allowed by either the satellite modem, or satellite network, or satellite
+     * server.
+     */
+    public static final int SATELLITE_SERVICE_OPERATION_NOT_ALLOWED = 29;
+    /**
+     * The radio technology is not supported by the satellite service provider.
+     */
+    public static final int SATELLITE_SERVICE_RADIO_TECHNOLOGY_NOT_SUPPORTED = 30;
+    /**
+     * SIM is absent.
+     */
+    public static final int SATELLITE_SERVICE_SIM_ABSENT = 31;
+    /**
+     * SIM is busy.
+     */
+    public static final int SATELLITE_SERVICE_SIM_BUSY = 32;
+    /**
+     * Received error from SIM card.
+     */
+    public static final int SATELLITE_SERVICE_SIM_ERR = 33;
+    /**
+     * The target EF is full.
+     */
+    public static final int SATELLITE_SERVICE_SIM_FULL = 34;
+    /**
+     * The subscription/user is not authorized to register with the satellite service provider.
+     */
+    public static final int SATELLITE_SERVICE_SUBSCRIBER_NOT_AUTHORIZED = 35;
+    /**
+     * The callback was already registered with Telephony framework.
+     */
+    public static final int SATELLITE_SERVICE_CALLBACK_ALREADY_REGISTERED = 36;
+    /**
+     * The callback was not registered with Telephony framework.
+     */
+    public static final int SATELLITE_SERVICE_CALLBACK_NOT_REGISTERED = 37;
+    /**
+     * The request cannot be performed since the subscriber/user's account balance is not
+     * sufficient.
+     */
+    public static final int SATELLITE_SERVICE_NOT_SUFFICIENT_ACCOUNT_BALANCE = 38;
+    /**
+     * While processing a request from the Telephony framework, the satellite modem detects
+     * terrestrial signal, aborts the request, and switches to the terrestrial network.
+     */
+    public static final int SATELLITE_SERVICE_SWITCHED_FROM_SATELLITE_TO_TERRESTRIAL = 39;
+    /**
+     * The subscriber/user is not registered with the service provider.
+     */
+    public static final int SATELLITE_SERVICE_UNIDENTIFIED_SUBSCRIBER = 40;
+    /**
+     * The contact to be added/removed is either not existing or not valid.
+     */
+    public static final int SATELLITE_SERVICE_INVALID_CONTACT = 41;
+    /**
+     * The encoding scheme is not supported by either the satellite provider or the device.
+     */
+    public static final int SATELLITE_SERVICE_ENCODING_NOT_SUPPORTED = 42;
+    /**
+     * Received error from the satellite network. This generic error code should be used only when
+     * the error cannot be mapped to other specific network error codes.
+     */
+    public static final int SATELLITE_SERVICE_NETWORK_ERROR = 43;
+    /**
+     * Modem hit unexpected error scenario while handling this request.
+     */
+    public static final int SATELLITE_SERVICE_MODEM_ERROR = 44;
 
     /** @hide */
     @IntDef(prefix = {"SATELLITE_SERVICE_"}, value = {
             SATELLITE_SERVICE_SUCCESS,
             SATELLITE_SERVICE_SERVER_NOT_REACHABLE,
             SATELLITE_SERVICE_SERVER_ERROR,
-            SATELLITE_SERVICE_TELEPHONY_INTERNAL_ERROR,
-            SATELLITE_SERVICE_MODEM_ERROR,
-            SATELLITE_SERVICE_SYSTEM_ERROR,
+            SATELLITE_SERVICE_INVALID_TELEPHONY_STATE,
+            SATELLITE_SERVICE_UNEXPECTED_MODEM_RESPONSE,
+            SATELLITE_SERVICE_RIL_ERROR,
             SATELLITE_SERVICE_INVALID_ARGUMENTS,
             SATELLITE_SERVICE_INVALID_MODEM_STATE,
             SATELLITE_SERVICE_INVALID_SIM_STATE,
-            SATELLITE_SERVICE_INVALID_STATE,
-            SATELLITE_SERVICE_NOT_AVAILABLE,
-            SATELLITE_SERVICE_NOT_SUPPORTED,
-            SATELLITE_SERVICE_RATE_LIMITED,
-            SATELLITE_SERVICE_NO_MEMORY,
+            SATELLITE_SERVICE_INVALID_RIL_STATE,
+            SATELLITE_SERVICE_RADIO_NOT_AVAILABLE,
+            SATELLITE_SERVICE_REQUEST_NOT_SUPPORTED,
+            SATELLITE_SERVICE_REQUEST_RATE_LIMITED,
             SATELLITE_SERVICE_NO_RESOURCES,
             SATELLITE_SERVICE_REQUEST_FAILED,
-            SATELLITE_SERVICE_INVALID_SUBSCRIPTION_ID,
             SATELLITE_SERVICE_ERROR,
-            SATELLITE_SERVICE_DISABLED
+            SATELLITE_SERVICE_DISABLED,
+            SATELLITE_SERVICE_ALREADY_PROVISIONED,
+            SATELLITE_SERVICE_PROVISION_IN_PROGRESS,
+            SATELLITE_SERVICE_REQUEST_ABORTED,
+            SATELLITE_SERVICE_ACCESS_BARRED,
+            SATELLITE_SERVICE_FEATURE_NOT_SUPPORTED,
+            SATELLITE_SERVICE_MODEM_INCOMPATIBLE,
+            SATELLITE_SERVICE_NETWORK_NOT_READY,
+            SATELLITE_SERVICE_SERVER_NOT_READY,
+            SATELLITE_SERVICE_SERVER_REJECT,
+            SATELLITE_SERVICE_NETWORK_TIMEOUT,
+            SATELLITE_SERVICE_NO_SATELLITE_SIGNAL,
+            SATELLITE_SERVICE_NO_SUBSCRIPTION,
+            SATELLITE_SERVICE_OPERATION_NOT_ALLOWED,
+            SATELLITE_SERVICE_RADIO_TECHNOLOGY_NOT_SUPPORTED,
+            SATELLITE_SERVICE_SIM_ABSENT,
+            SATELLITE_SERVICE_SIM_BUSY,
+            SATELLITE_SERVICE_SIM_ERR,
+            SATELLITE_SERVICE_SIM_FULL,
+            SATELLITE_SERVICE_SUBSCRIBER_NOT_AUTHORIZED,
+            SATELLITE_SERVICE_CALLBACK_ALREADY_REGISTERED,
+            SATELLITE_SERVICE_CALLBACK_NOT_REGISTERED,
+            SATELLITE_SERVICE_NOT_SUFFICIENT_ACCOUNT_BALANCE,
+            SATELLITE_SERVICE_SWITCHED_FROM_SATELLITE_TO_TERRESTRIAL,
+            SATELLITE_SERVICE_UNIDENTIFIED_SUBSCRIBER,
+            SATELLITE_SERVICE_INVALID_CONTACT,
+            SATELLITE_SERVICE_ENCODING_NOT_SUPPORTED,
+            SATELLITE_SERVICE_NETWORK_ERROR,
+            SATELLITE_SERVICE_MODEM_ERROR
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface SatelliteServiceResult {}
 
     /**
+     * Power on or off the satellite modem.
+     *
+     * @param powerOn {@code true} to power on the satellite modem and {@code false} to power off.
+     *
+     * @throws SecurityException if the caller doesn't have required permission.
+     * @throws IllegalStateException if the Telephony process is not currently available.
+     *
+     * @return The result of the operation.
+     */
+    @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+    @SatelliteServiceResult public int setSatellitePower(boolean powerOn) {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                return telephony.setSatellitePower(mSubId, powerOn);
+            } else {
+                throw new IllegalStateException("telephony service is null.");
+            }
+        } catch (RemoteException ex) {
+            Rlog.e(TAG, "setSatellitePower RemoteException", ex);
+            ex.rethrowFromSystemServer();
+        }
+        return SATELLITE_SERVICE_REQUEST_FAILED;
+    }
+
+    /**
+     * Check whether the satellite modem is powered on.
+     *
+     * @param executor The executor on which the result listener will be called.
+     * @param resultListener Listener with the result if the operation is successful.
+     *                       If this method returns {@link #SATELLITE_SERVICE_SUCCESS}, the result
+     *                       listener will return {@code true} if the satellite modem is powered on
+     *                       and {@code false} otherwise.
+     *
+     * @throws SecurityException if the caller doesn't have required permission.
+     * @throws IllegalStateException if the Telephony process is not currently available.
+     *
+     * @return The result of the operation.
+     */
+    @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+    @SatelliteServiceResult public int isSatellitePowerOn(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull Consumer<Boolean> resultListener) {
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(resultListener);
+
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                IBooleanConsumer internalCallback = new IBooleanConsumer.Stub() {
+                    @Override
+                    public void accept(boolean result) {
+                        executor.execute(() -> Binder.withCleanCallingIdentity(
+                                () -> resultListener.accept(result)));
+                    }
+                };
+
+                return telephony.isSatellitePowerOn(mSubId, internalCallback);
+            } else {
+                throw new IllegalStateException("telephony service is null.");
+            }
+        } catch (RemoteException ex) {
+            loge("isSatellitePowerOn() RemoteException:" + ex);
+            ex.rethrowFromSystemServer();
+        }
+        return SATELLITE_SERVICE_REQUEST_FAILED;
+    }
+
+    /**
+     * Check whether the satellite service is supported on the device.
+     *
+     * @param executor The executor on which the result listener will be called.
+     * @param resultListener Listener with the result if the operation is successful.
+     *                       If this method returns {@link #SATELLITE_SERVICE_SUCCESS}, the result
+     *                       listener will return {@code true} if the satellite service is supported
+     *                       and {@code false} otherwise.
+     *
+     * @throws SecurityException if the caller doesn't have required permission.
+     * @throws IllegalStateException if the Telephony process is not currently available.
+     *
+     * @return The result of the operation.
+     */
+    @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+    @SatelliteServiceResult public int isSatelliteSupported(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull Consumer<Boolean> resultListener) {
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(resultListener);
+
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                IBooleanConsumer internalCallback = new IBooleanConsumer.Stub() {
+                    @Override
+                    public void accept(boolean result) {
+                        executor.execute(() -> Binder.withCleanCallingIdentity(
+                                () -> resultListener.accept(result)));
+                    }
+                };
+
+                return telephony.isSatelliteSupported(mSubId, internalCallback);
+            } else {
+                throw new IllegalStateException("telephony service is null.");
+            }
+        } catch (RemoteException ex) {
+            loge("isSatelliteSupported() RemoteException:" + ex);
+            ex.rethrowFromSystemServer();
+        }
+        return SATELLITE_SERVICE_REQUEST_FAILED;
+    }
+
+    /**
+     * Get the {@link SatelliteCapabilities} with all capabilities of the satellite service.
+     *
+     * @param executor The executor on which the result listener will be called.
+     * @param resultListener Listener with the result if the operation is successful.
+     *                       If this method returns {@link #SATELLITE_SERVICE_SUCCESS}, the result
+     *                       listener will return the current {@link SatelliteCapabilities}.
+     *
+     * @throws SecurityException if the caller doesn't have required permission.
+     * @throws IllegalStateException if the Telephony process is not currently available.
+     *
+     * @return The result of the operation.
+     */
+    @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+    @SatelliteServiceResult public int getSatelliteCapabilities(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull Consumer<SatelliteCapabilities> resultListener) {
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(resultListener);
+
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                ISatelliteCapabilitiesConsumer internalCallback =
+                        new ISatelliteCapabilitiesConsumer.Stub() {
+                    @Override
+                    public void accept(SatelliteCapabilities result) {
+                        executor.execute(() -> Binder.withCleanCallingIdentity(
+                                () -> resultListener.accept(result)));
+                    }
+                };
+
+                return telephony.getSatelliteCapabilities(mSubId, internalCallback);
+            } else {
+                throw new IllegalStateException("telephony service is null.");
+            }
+        } catch (RemoteException ex) {
+            loge("getSatelliteCapabilities() RemoteException:" + ex);
+            ex.rethrowFromSystemServer();
+        }
+        return SATELLITE_SERVICE_REQUEST_FAILED;
+    }
+
+    /**
      * Message transfer is waiting to acquire.
      */
     public static final int SATELLITE_MESSAGE_TRANSFER_STATE_WAITING_TO_ACQUIRE = 0;
@@ -241,73 +529,33 @@
     public @interface SatelliteMessageTransferState {}
 
     /**
-     * Callback for position updates from the satellite service.
-     */
-    public interface SatellitePositionUpdateCallback {
-        /**
-         * Called when the satellite position changes.
-         *
-         * @param pointingInfo The pointing info containing the satellite location.
-         */
-        void onSatellitePositionUpdate(@NonNull PointingInfo pointingInfo);
-
-        /**
-         * Called when satellite message transfer state changes.
-         *
-         * @param state The new message transfer state.
-         */
-        void onMessageTransferStateUpdate(@SatelliteMessageTransferState int state);
-    }
-
-    /**
      * Start receiving satellite position updates.
      * This can be called by the pointing UI when the user starts pointing to the satellite.
      * Modem should continue to report the pointing input as the device or satellite moves.
      * Satellite position updates are started only on {@link #SATELLITE_SERVICE_SUCCESS}.
      * All other results indicate that this operation failed.
      *
-     * @param executor The executor to run callbacks on.
-     * @param callback The callback to notify of changes in satellite position.
+     * @param executor The executor on which the callback will be called.
+     * @param callback The callback to notify of changes in satellite position. This
+     *                 SatelliteCallback should implement the interface
+     *                 {@link SatelliteCallback.SatellitePositionUpdateListener}.
+     *
+     * @throws SecurityException if the caller doesn't have required permission.
+     * @throws IllegalStateException if the Telephony process is not currently available.
+     *
      * @return The result of the operation.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     @SatelliteServiceResult public int startSatellitePositionUpdates(
-            @NonNull @CallbackExecutor Executor executor,
-            @NonNull SatellitePositionUpdateCallback callback) {
+            @NonNull Executor executor, @NonNull SatelliteCallback callback) {
         Objects.requireNonNull(executor);
         Objects.requireNonNull(callback);
 
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null) {
-                int id;
-                if (mSatellitePositionUpdateCallbacks.containsKey(callback)) {
-                    id = mSatellitePositionUpdateCallbacks.get(callback);
-                } else {
-                    id = mSatellitePositionUpdateCallbackId.getAndIncrement();
-                }
-                int result = telephony.startSatellitePositionUpdates(mSubId, id,
-                        new ISatellitePositionUpdateCallback.Stub() {
-                            @Override
-                            public void onSatellitePositionUpdate(
-                                    @NonNull PointingInfo pointingInfo) {
-                                logd("onSatellitePositionUpdate: pointingInfo=" + pointingInfo);
-                                executor.execute(() -> Binder.withCleanCallingIdentity(
-                                        () -> callback.onSatellitePositionUpdate(pointingInfo)));
-                            }
-
-                            @Override
-                            public void onMessageTransferStateUpdate(
-                                    @SatelliteMessageTransferState int state) {
-                                logd("onMessageTransferStateUpdate: state=" + state);
-                                executor.execute(() -> Binder.withCleanCallingIdentity(
-                                        () -> callback.onMessageTransferStateUpdate(state)));
-                            }
-                        });
-                if (result == SATELLITE_SERVICE_SUCCESS) {
-                    mSatellitePositionUpdateCallbacks.put(callback, id);
-                }
-                return result;
+                callback.init(executor);
+                return telephony.startSatellitePositionUpdates(mSubId, callback.getCallbackStub());
             } else {
                 throw new IllegalStateException("telephony service is null.");
             }
@@ -325,30 +573,24 @@
      * All other results indicate that this operation failed.
      *
      * @param callback The callback that was passed in {@link
-     *                 #startSatellitePositionUpdates(Executor, SatellitePositionUpdateCallback)}.
-     * @return The result of the operation.
+     *                 #startSatellitePositionUpdates(Executor, SatelliteCallback)}.
+     *
+     * @throws SecurityException if the caller doesn't have required permission.
      * @throws IllegalArgumentException if the callback is invalid.
+     * @throws IllegalStateException if the Telephony process is not currently available.
+     *
+     * @return The result of the operation.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     @SatelliteServiceResult public int stopSatellitePositionUpdates(
-            @NonNull SatellitePositionUpdateCallback callback) {
+            @NonNull SatelliteCallback callback) {
         Objects.requireNonNull(callback);
 
-        if (!mSatellitePositionUpdateCallbacks.containsKey(callback)) {
-            throw new IllegalArgumentException(
-                    "startSatellitePositionUpdates was never called with the callback provided.");
-        }
-
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null) {
-                int result = telephony.stopSatellitePositionUpdates(mSubId,
-                        mSatellitePositionUpdateCallbacks.get(callback));
-                if (result == SATELLITE_SERVICE_SUCCESS) {
-                    mSatellitePositionUpdateCallbacks.remove(callback);
-                    // TODO: Notify SmsHandler that pointing UI stopped
-                }
-                return result;
+                return telephony.stopSatellitePositionUpdates(mSubId, callback.getCallbackStub());
+                // TODO: Notify SmsHandler that pointing UI stopped
             } else {
                 throw new IllegalStateException("telephony service is null.");
             }
@@ -367,8 +609,9 @@
      *                       will be called with maximum characters limit.
      *
      * @throws SecurityException if the caller doesn't have required permission.
+     * @throws IllegalStateException if the Telephony process is not currently available.
      *
-     * @return The result of the operation
+     * @return The result of the operation.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     @SatelliteServiceResult
@@ -399,6 +642,164 @@
         return SATELLITE_SERVICE_REQUEST_FAILED;
     }
 
+
+    /**
+     * Register the subscription with a satellite provider. This is needed if the provider allows
+     * dynamic registration.
+     *
+     * @param features List of features to be provisioned.
+     * @param executor The optional executor to run callbacks on.
+     * @param callback The optional callback to get the error code of the request.
+     * @param cancellationSignal The optional signal used by the caller to cancel the provision
+     *                           request. Even when the cancellation is signaled, Telephony will
+     *                           still trigger the callback to return the result of this request.
+     * @throws SecurityException if the caller doesn't have required permission.
+     * @throws IllegalStateException if the Telephony process is not currently available.
+     */
+    @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+    public void provisionSatelliteService(
+            @NonNull @SatelliteImplBase.Feature int[] features,
+            @Nullable @CallbackExecutor Executor executor,
+            @SatelliteServiceResult @Nullable Consumer<Integer> callback,
+            @Nullable CancellationSignal cancellationSignal) {
+        Objects.requireNonNull(features);
+
+        ICancellationSignal cancelRemote = null;
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                IIntegerConsumer callbackStub = new IIntegerConsumer.Stub() {
+                    @Override
+                    public void accept(int result) {
+                        if (executor == null || callback == null) {
+                            logd("provisionSatelliteService: executor and/or callback is null");
+                            return;
+                        }
+                        Binder.withCleanCallingIdentity(() -> {
+                            executor.execute(() -> callback.accept(result));
+                        });
+                    }
+                };
+                cancelRemote = telephony.provisionSatelliteService(mSubId, features, callbackStub);
+            } else {
+                throw new IllegalStateException("telephony service is null.");
+            }
+        } catch (RemoteException ex) {
+            loge("provisionSatelliteService RemoteException=" + ex);
+            ex.rethrowFromSystemServer();
+        }
+        if (cancellationSignal != null) {
+            cancellationSignal.setRemote(cancelRemote);
+        }
+    }
+
+    /**
+     * Register for the satellite provision state change.
+     *
+     * @param executor - The executor on which the callback will be called.
+     * @param callback The callback to handle the satellite provision state changed event. This
+     *                 SatelliteCallback should implement the interface
+     *                 {@link SatelliteCallback.SatelliteProvisionStateListener}.
+     * @return The error code of the request.
+     * @throws SecurityException if the caller doesn't have required permission.
+     * @throws IllegalStateException if the Telephony process is not currently available.
+     */
+    @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+    @SatelliteServiceResult
+    public int registerForSatelliteProvisionStateChanged(
+            @NonNull Executor executor, @NonNull SatelliteCallback callback) {
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(callback);
+
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                callback.init(executor);
+                return telephony.registerForSatelliteProvisionStateChanged(
+                        mSubId, callback.getCallbackStub());
+            } else {
+                throw new IllegalStateException("telephony service is null.");
+            }
+        } catch (RemoteException ex) {
+            loge("registerForSatelliteProvisionStateChanged RemoteException: " + ex);
+            ex.rethrowFromSystemServer();
+        }
+        return SATELLITE_SERVICE_REQUEST_FAILED;
+    }
+
+    /**
+     * Unregister for the satellite provision state change.
+     *
+     * @param callback The callback that was passed to
+     * {@link #registerForSatelliteProvisionStateChanged(Executor, SatelliteCallback)}
+     * @return The error code of the request.
+     * @throws SecurityException if the caller doesn't have required permission.
+     * @throws IllegalStateException if the Telephony process is not currently available.
+     */
+    @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+    @SatelliteServiceResult
+    public int unregisterForSatelliteProvisionStateChanged(@NonNull SatelliteCallback callback) {
+        Objects.requireNonNull(callback);
+
+        if (callback.getCallbackStub() == null) {
+            loge("unregisterForSatelliteProvisionStateChanged: callbackStub is null");
+            return SATELLITE_SERVICE_CALLBACK_NOT_REGISTERED;
+        }
+
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                return telephony.unregisterForSatelliteProvisionStateChanged(
+                        mSubId, callback.getCallbackStub());
+            } else {
+                throw new IllegalStateException("telephony service is null.");
+            }
+        } catch (RemoteException ex) {
+            loge("unregisterForSatelliteProvisionStateChanged RemoteException: " + ex);
+            ex.rethrowFromSystemServer();
+        }
+        return SATELLITE_SERVICE_REQUEST_FAILED;
+    }
+
+    /**
+     * Get the list of provisioned satellite features.
+     *
+     * @param executor The executor to run callbacks on.
+     * @param resultListener The callback to get the list of provisioned features when the request
+     *                       returns success result.
+     * @return The error code of the request.
+     * @throws SecurityException if the caller doesn't have required permission.
+     * @throws IllegalStateException if the Telephony process is not currently available.
+     */
+    @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+    @SatelliteServiceResult
+    public int getProvisionedSatelliteFeatures(
+            @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<int[]> resultListener) {
+        Objects.requireNonNull(resultListener);
+        Objects.requireNonNull(executor);
+
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                IIntArrayConsumer callbackStub = new IIntArrayConsumer.Stub() {
+                    @Override
+                    public void accept(int[] result) {
+                        Binder.withCleanCallingIdentity(() -> {
+                            executor.execute(() -> resultListener.accept(result));
+                        });
+                    }
+                };
+                return telephony.getProvisionedSatelliteFeatures(mSubId, callbackStub);
+            } else {
+                throw new IllegalStateException("telephony service is null.");
+            }
+        } catch (RemoteException ex) {
+            loge("getProvisionedSatelliteFeatures() RemoteException:" + ex);
+            ex.rethrowFromSystemServer();
+        }
+        return SATELLITE_SERVICE_REQUEST_FAILED;
+    }
+
     private static ITelephony getITelephony() {
         ITelephony binder = ITelephony.Stub.asInterface(TelephonyFrameworkInitializer
                 .getTelephonyServiceManager()
diff --git a/telephony/java/com/android/internal/telephony/IIntArrayConsumer.aidl b/telephony/java/com/android/internal/telephony/IIntArrayConsumer.aidl
new file mode 100644
index 0000000..c208755
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/IIntArrayConsumer.aidl
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony;
+
+// Copies consumer pattern for an operation that requires an int array result from another
+// process to finish.
+oneway interface IIntArrayConsumer {
+    void accept(in int[] result);
+}
\ No newline at end of file
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 5486365..baf45cd 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -21,6 +21,7 @@
 import android.content.Intent;
 import android.content.IntentSender;
 import android.os.Bundle;
+import android.os.ICancellationSignal;
 import android.os.IBinder;
 import android.os.Messenger;
 import android.os.ParcelFileDescriptor;
@@ -66,13 +67,16 @@
 import android.telephony.ims.aidl.IImsRegistration;
 import android.telephony.ims.aidl.IImsRegistrationCallback;
 import android.telephony.ims.aidl.IRcsConfigCallback;
-import android.telephony.satellite.ISatellitePositionUpdateCallback;
+import android.telephony.satellite.ISatelliteStateListener;
+import android.telephony.satellite.ISatelliteCapabilitiesConsumer;
+import android.telephony.satellite.SatelliteCapabilities;
 import com.android.ims.internal.IImsServiceFeatureCallback;
 import com.android.internal.telephony.CellNetworkScanResult;
 import com.android.internal.telephony.IBooleanConsumer;
 import com.android.internal.telephony.ICallForwardingInfoCallback;
 import com.android.internal.telephony.IccLogicalChannelRequest;
 import com.android.internal.telephony.IImsStateCallback;
+import com.android.internal.telephony.IIntArrayConsumer;
 import com.android.internal.telephony.IIntegerConsumer;
 import com.android.internal.telephony.INumberVerificationCallback;
 import com.android.internal.telephony.OperatorInfo;
@@ -1534,7 +1538,7 @@
      */
     CarrierRestrictionRules getAllowedCarriers();
 
-   /**
+    /**
      * Returns carrier id of the given subscription.
      * <p>To recognize carrier as a first class identity, assign each carrier with a canonical
      * integer a.k.a carrier id.
@@ -2700,18 +2704,74 @@
     void getCarrierRestrictionStatus(IIntegerConsumer internalCallback, String packageName);
 
     /**
+     * Power on or off the satellite modem.
+     */
+    int setSatellitePower(int subId, boolean powerOn);
+
+    /**
+     * Check whether the satellite modem is powered on.
+     */
+    int isSatellitePowerOn(int subId, IBooleanConsumer internalCallback);
+
+    /**
+     * Check whether the satellite service is supported on the device.
+     */
+    int isSatelliteSupported(int subId, IBooleanConsumer internalCallback);
+
+    /**
+     * Get the capabilities of the satellite service.
+     */
+    int getSatelliteCapabilities(int subId, ISatelliteCapabilitiesConsumer internalCallback);
+
+    /**
      * Start receiving satellite pointing updates.
      */
-    int startSatellitePositionUpdates(int subId, int callbackId,
-            in ISatellitePositionUpdateCallback callback);
+    int startSatellitePositionUpdates(int subId, in ISatelliteStateListener callback);
 
     /**
      * Stop receiving satellite pointing updates.
      */
-    int stopSatellitePositionUpdates(int subId, int callbackId);
+    int stopSatellitePositionUpdates(int subId, ISatelliteStateListener callback);
 
     /**
      * Get maximum number of characters per text message on satellite.
      */
     int getMaxCharactersPerSatelliteTextMessage(int subId, IIntegerConsumer internalCallback);
-}
\ No newline at end of file
+
+    /**
+     * Register the subscription with a satellite provider.
+     * This is needed to register the subscription if the provider allows dynamic registration.
+     *
+     * @param subId The subId of the subscription to be provisioned.
+     * @param features List of features to be provisioned.
+     * @param callback The callback to get the error code of the request.
+     * @return The signal transport used by callers to cancel the provision request.
+     */
+    ICancellationSignal provisionSatelliteService(int subId, in int[] features,
+            in IIntegerConsumer callback);
+
+    /**
+     * Register for the satellite provision state change.
+     *
+     * @param subId The subId of the subscription to be provisioned.
+     * @param callback The callback to handle the satellite provision state changed event.
+     */
+    int registerForSatelliteProvisionStateChanged(int subId, ISatelliteStateListener callback);
+
+    /**
+     * Unregister for the satellite provision state change.
+     *
+     * @param subId The subId of the subscription associated with the satellite service.
+     * @param callback The callback that was passed to
+     *                   registerForSatelliteProvisionStateChanged.
+     */
+    int unregisterForSatelliteProvisionStateChanged(int subId, ISatelliteStateListener callback);
+
+    /**
+     * Get the list of provisioned satellite features.
+     *
+     * @param subId The subId of the subscription to be provisioned.
+     * @param callback The callback to get the list of provisioned satellite features.
+     */
+    int getProvisionedSatelliteFeatures(int subId, IIntArrayConsumer callback);
+}
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index 6e56963..45daab3 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -120,6 +120,31 @@
     int BLOCKED_DUE_TO_CALL = 69;                   /* SMS is blocked due to call control */
     int RF_HARDWARE_ISSUE = 70;                     /* RF HW issue is detected */
     int NO_RF_CALIBRATION_INFO = 71;                /* No RF calibration in device */
+    int ENCODING_NOT_SUPPORTED = 72;                /* The encoding scheme is not supported by
+                                                       either the network or the MS. */
+    int FEATURE_NOT_SUPPORTED = 73;                 /* The requesting feature is not supported by
+                                                       the service provider. */
+    int INVALID_CONTACT = 74;                       /* The contact to be added is either not
+                                                       existing or not valid. */
+    int MODEM_INCOMPATIBLE = 75;                    /* The modem of the MS is not compatible with
+                                                       the service provider. */
+    int NETWORK_TIMEOUT = 76;                       /* Modem timeout to receive ACK or response from
+                                                       network after sending a request to it. */
+    int NO_SATELLITE_SIGNAL = 77;                   /* Modem fails to communicate with the satellite
+                                                       network since there is no satellite signal.*/
+    int NOT_SUFFICIENT_ACCOUNT_BALANCE = 78;        /* The request cannot be performed since the
+                                                       subscriber's account balance is not
+                                                       sufficient. */
+    int RADIO_TECHNOLOGY_NOT_SUPPORTED = 79;        /* The radio technology is not supported by the
+                                                       service provider. */
+    int SUBSCRIBER_NOT_AUTHORIZED = 80;             /* The subscription is not authorized to
+                                                       register with the service provider. */
+    int SWITCHED_FROM_SATELLITE_TO_TERRESTRIAL = 81; /* While processing a request from the
+                                                       Framework the satellite modem detects
+                                                       terrestrial signal, aborts the request, and
+                                                       switches to the terrestrial network. */
+    int UNIDENTIFIED_SUBSCRIBER = 82;               /* The subscriber is not registered with the
+                                                       service provider */
 
     // Below is list of OEM specific error codes which can by used by OEMs in case they don't want to
     // reveal particular replacement for Generic failure
diff --git a/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java b/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java
index b8e9a17..db36975 100644
--- a/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java
+++ b/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java
@@ -22,6 +22,9 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import android.platform.test.annotations.LargeTest;
+import android.platform.test.annotations.Presubmit;
+
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
@@ -36,7 +39,7 @@
 
 import java.util.concurrent.TimeUnit;
 
-// TODO: Add @Presubmit
+@Presubmit
 @RunWith(DeviceJUnit4ClassRunner.class)
 public final class BinaryTransparencyHostTest extends BaseHostJUnit4Test {
     private static final String PACKAGE_NAME = "android.transparency.test.app";
@@ -103,6 +106,7 @@
         }
     }
 
+    @LargeTest
     @Test
     public void testRebootlessApexUpdateTriggersJobScheduling() throws Exception {
         try {
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/MeshActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/MeshActivity.java
index 413f92c..ae3dcb8 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/MeshActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/MeshActivity.java
@@ -32,7 +32,6 @@
 
 import java.nio.FloatBuffer;
 import java.nio.ShortBuffer;
-import java.util.ArrayList;
 
 public class MeshActivity extends Activity {
     @Override
@@ -115,9 +114,11 @@
                     + "      color = vec4(1.0, 0.0, 0.0, 1.0);"
                     + "      return varyings.position;\n"
                     + "}";
-            ArrayList<Attribute> attList = new ArrayList<>();
-            attList.add(new Attribute(MeshSpecification.FLOAT2, 0, "position"));
-            ArrayList<Varying> varyList = new ArrayList<>();
+            Attribute[] attList = new Attribute[]{
+                    new Attribute(MeshSpecification.TYPE_FLOAT2, 0, "position"),
+
+            };
+            Varying[] varyList = new Varying[0];
             return MeshSpecification.make(attList, 8, varyList, vs, fs);
         }
     }
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/MeshLargeActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/MeshLargeActivity.java
index e62db6b..01ca2fc 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/MeshLargeActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/MeshLargeActivity.java
@@ -32,7 +32,6 @@
 
 import java.nio.FloatBuffer;
 import java.nio.ShortBuffer;
-import java.util.ArrayList;
 
 public class MeshLargeActivity extends Activity {
     @Override
@@ -131,44 +130,37 @@
                     + "      color = vec4(1.0, 0.0, 0.0, 1.0);"
                     + "      return varyings.position;\n"
                     + "}";
-            ArrayList<Attribute> attList = new ArrayList<>();
-            attList.add(new Attribute(MeshSpecification.FLOAT2, 0, "position"));
-            attList.add(new Attribute(
-                    MeshSpecification.FLOAT4,
-                    8,
-                    "test"
-            ));
-            attList.add(new Attribute(
-                    MeshSpecification.FLOAT4,
-                    24,
-                    "test2"
-            ));
-            attList.add(new Attribute(
-                    MeshSpecification.FLOAT4,
-                    40,
-                    "test3"
-            ));
-            attList.add(new Attribute(
-                    MeshSpecification.FLOAT4,
-                    56,
-                    "test4"
-            ));
-            attList.add(new Attribute(
-                    MeshSpecification.FLOAT4,
-                    72,
-                    "test5"
-            ));
-            attList.add(new Attribute(
-                    MeshSpecification.FLOAT4,
-                    88,
-                    "test6"
-            ));
-            attList.add(new Attribute(
-                    MeshSpecification.FLOAT4,
-                    104,
-                    "test7"
-            ));
-            ArrayList<Varying> varyList = new ArrayList<>();
+            Attribute[] attList = new Attribute[]{
+                    new Attribute(MeshSpecification.TYPE_FLOAT2, 0, "position"),
+                    new Attribute(MeshSpecification.TYPE_FLOAT4, 8, "test"),
+                    new Attribute(MeshSpecification.TYPE_FLOAT4, 24, "test2"),
+                    new Attribute(
+                            MeshSpecification.TYPE_FLOAT4,
+                            40,
+                            "test3"
+                    ),
+                    new Attribute(
+                            MeshSpecification.TYPE_FLOAT4,
+                            56,
+                            "test4"
+                    ),
+                    new Attribute(
+                            MeshSpecification.TYPE_FLOAT4,
+                            72,
+                            "test5"
+                    ),
+                    new Attribute(
+                            MeshSpecification.TYPE_FLOAT4,
+                            88,
+                            "test6"
+                    ),
+                    new Attribute(
+                            MeshSpecification.TYPE_FLOAT4,
+                            104,
+                            "test7"
+                    )
+            };
+            Varying[] varyList = new Varying[0];
             return MeshSpecification.make(attList, 120, varyList, vs, fs);
         }
     }
diff --git a/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java b/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java
index 4103ca7..210e3ea 100644
--- a/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java
+++ b/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java
@@ -148,6 +148,10 @@
         public void getUsbSpeedCb(int speed){
         }
 
+        @Override
+        public void resetCb(int status){
+        }
+
     }
 
     @Before